none
ListBoxのアイテムテンプレートをListBoxにしたときに、バインディングして表示された子のListBoxから座標や描画サイズを取得したい RRS feed

  • 質問

  • 以下のようなXamlを記述したときに、Itemsというプロパティをもつクラスのコレクションをバインディングすると、

    親のリストボックスの中に、子のリストボックスが並んで表示されます。

    その後、やりたい事として、子のリストボックスの座標やActualHeightを取得したいです。

    <Grid x:Name="scrollGrid">
        <ListBox x:Name="TransportList" Margin="50" BorderThickness="0" Loaded="TransportList_Loaded"
                 ItemsSource="{Binding}" HorizontalAlignment="Right">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <ListBox ItemsSource="{Binding Path=Items}"
                             Background="GreenYellow"
                             BorderBrush="Green"
                             BorderThickness="2.0"
                             Margin="5.0"
                             PreviewMouseLeftButtonDown="aaa_MouseLeftButtonDown"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>

    しかし、イベント処理に以下のようなコードを実行すると、PointToScreenで例外が発生してしまいます。

    「型 'System.InvalidOperationException' のハンドルされていない例外が PresentationCore.dll で発生しました
    追加情報:この Visual は、PresentationSource に接続されていません。」

    おそらく、表示されているListBoxそのものを取得できていないためかと思いますが、

    表示されているListBoxのオブジェクトをどのように取得すればよいでしょうか?

    // すべての便ListBoxに対してポインタと重なるかチェック
    foreach (ListBox workBox in this.TransportList.Items)
    {
        var a=workBox.IsVisible;
    
        // 相対座標が0以上、幅以下、高さ以下であるかチェック
        Point relativePos = e.GetPosition(workBox);
        if (0 <= relativePos.X &&
            0 <= relativePos.Y &&
            relativePos.X <= workBox.ActualWidth &&
            relativePos.Y <= workBox.ActualHeight)
        {
            // 対象の便に荷物を追加できるかチェック
    
            Point leftCenter = new Point(0, workBox.ActualHeight / 2.0);
            Point screenPos = workBox.PointToScreen(leftCenter);
            Point gridPos = this.scrollGrid.PointFromScreen(screenPos);
    
            Line workLine = new Line
            {
                X1 = buttonDownPos.X,
                Y1 = buttonDownPos.Y,
                X2 = gridPos.X,
                Y2 = gridPos.Y,
                StrokeThickness = 1,
                Stroke = Brushes.Blue,
            };
            this.scrollGrid.Children.Add(workLine);
            break;
        }
    }

    2015年8月24日 4:34

回答

  • 情報が不完全なので、再現コードも修正コードも作れないです。
    (Frameworkバージョンは?どういう状態でどのイベントからこのコードが実行されるのか?buttonDownPosが何か?TransportListList.ItemsからListBoxは取れるはずがないのでどうやっているのか?)
    そのうえでの推測です。

    外側のListBoxは見えている設定だけだと仮想化が行われているはずなので、内側のListBoxのすべてが常に存在するとは限りません。

    画面外にある子要素は仮想化により必要になるまで生成されず、不要になれば削除または使い回しが行われ、一度取得した子要素をどこかに保持していても親のListBoxの子要素ではなくなっている場合があります。
    そのため、仮想化が有効な状態では、見えている要素以外に対しては存在しないかもしれない可能性を考慮する必要があります。

    以上で説明したように子要素がどこにも配置されていない、つまり親子関係が切断されていて親がいない状態になりえます。
    あるいは直接の親とは接続されていても、その親から相対座標計算対象(PointToScreenの場合はWindow)に到達できない場合でも同様です。
    その状態ではどこにも配置されていないのだから座標計算を行うことは不可能であり、エラーになることはわかると思います。
    つまり、こばっちょさんがPointToScreenを行った時のListBoxは親あるいは祖先の何処かが切れている状態になっているはずです。

    状態としては以下のようにすると同じエラーを出せます。

    ListBox listBox = new ListBox();
    listBox.PointToScreen(new Point(0,0));

    これはListBoxは作られているけれど、どこにも配置されておらず、親がいないので、座標計算に失敗します。

    とりあえず、4.5以降なら

    <ListBox x:Name="TransportList" Margin="50" BorderThickness="0" Loaded="TransportList_Loaded"
        ItemsSource="{Binding}" HorizontalAlignment="Right"
        VirtualizingPanel.IsVirtualizing="false">
      <!--(略)-->
    </ListBox>
    のようにして仮想化を無効にしてやればすべての子要素が存在するようになります。
    4.5未満なら
    <ListBox x:Name="TransportList" Margin="50" BorderThickness="0" Loaded="TransportList_Loaded"
        ItemsSource="{Binding}" HorizontalAlignment="Right">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
      <!--(略)-->
    </ListBox>
    のようにして、ItemsPanelTemplateを仮想化なしにしてやればいいです。

    エラーが出なければいいのであればPointToScreenやPointFromScreenを使わずに

    //Point screenPos = workBox.PointToScreen(leftCenter);
    //Point gridPos = this.scrollGrid.PointFromScreen(screenPos);
    
    Point gridPos = workBox.TranslatePoint(leftCenter, scrollGrid);
    のようにTranslatePointを使えばInvalidOperationExceptionにならなかったりします。(エラーにならないだけです)

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答の候補に設定 星 睦美 2015年8月26日 5:50
    • 回答としてマーク 星 睦美 2015年9月3日 4:49
    2015年8月24日 11:49

すべての返信

  • 情報が不完全なので、再現コードも修正コードも作れないです。
    (Frameworkバージョンは?どういう状態でどのイベントからこのコードが実行されるのか?buttonDownPosが何か?TransportListList.ItemsからListBoxは取れるはずがないのでどうやっているのか?)
    そのうえでの推測です。

    外側のListBoxは見えている設定だけだと仮想化が行われているはずなので、内側のListBoxのすべてが常に存在するとは限りません。

    画面外にある子要素は仮想化により必要になるまで生成されず、不要になれば削除または使い回しが行われ、一度取得した子要素をどこかに保持していても親のListBoxの子要素ではなくなっている場合があります。
    そのため、仮想化が有効な状態では、見えている要素以外に対しては存在しないかもしれない可能性を考慮する必要があります。

    以上で説明したように子要素がどこにも配置されていない、つまり親子関係が切断されていて親がいない状態になりえます。
    あるいは直接の親とは接続されていても、その親から相対座標計算対象(PointToScreenの場合はWindow)に到達できない場合でも同様です。
    その状態ではどこにも配置されていないのだから座標計算を行うことは不可能であり、エラーになることはわかると思います。
    つまり、こばっちょさんがPointToScreenを行った時のListBoxは親あるいは祖先の何処かが切れている状態になっているはずです。

    状態としては以下のようにすると同じエラーを出せます。

    ListBox listBox = new ListBox();
    listBox.PointToScreen(new Point(0,0));

    これはListBoxは作られているけれど、どこにも配置されておらず、親がいないので、座標計算に失敗します。

    とりあえず、4.5以降なら

    <ListBox x:Name="TransportList" Margin="50" BorderThickness="0" Loaded="TransportList_Loaded"
        ItemsSource="{Binding}" HorizontalAlignment="Right"
        VirtualizingPanel.IsVirtualizing="false">
      <!--(略)-->
    </ListBox>
    のようにして仮想化を無効にしてやればすべての子要素が存在するようになります。
    4.5未満なら
    <ListBox x:Name="TransportList" Margin="50" BorderThickness="0" Loaded="TransportList_Loaded"
        ItemsSource="{Binding}" HorizontalAlignment="Right">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
      <!--(略)-->
    </ListBox>
    のようにして、ItemsPanelTemplateを仮想化なしにしてやればいいです。

    エラーが出なければいいのであればPointToScreenやPointFromScreenを使わずに

    //Point screenPos = workBox.PointToScreen(leftCenter);
    //Point gridPos = this.scrollGrid.PointFromScreen(screenPos);
    
    Point gridPos = workBox.TranslatePoint(leftCenter, scrollGrid);
    のようにTranslatePointを使えばInvalidOperationExceptionにならなかったりします。(エラーにならないだけです)

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答の候補に設定 星 睦美 2015年8月26日 5:50
    • 回答としてマーク 星 睦美 2015年9月3日 4:49
    2015年8月24日 11:49
  • フォーラム オペレーターの星 睦美です。

    gekka さん、いつも丁寧な回答をありがとうございます。

    こばっちょ さん、今回は投稿ありがとうございます。
    gekka さんからの返信が参考になったのではないかと思います。gekka さんも回答に、推測ですと書かれていますので、もし回答の内容に疑問な点やさらに質問したい点がありましたら[回答としてのマークの解除] をして返信いただければと思います。

    今後ともMSDN フォーラムをよろしくお願い致します。


    フォーラム オペレーター 星 睦美 - MSDN Community Support


    • 編集済み 星 睦美 2015年9月3日 4:49 編集
    2015年9月3日 4:49