2009-10-04 6 views
55

私は非常に大きなツリーを表示しており、そこにはたくさんのアイテムがあります。これらの各アイテムは、関連するUserControlコントロールを介してユーザーに情報を表示します。この情報は、250ミリ秒ごとに更新する必要があります。これは、リフレクションを使用して値の一部にアクセスしているため、私の最初のアプローチは、IsVisibleプロパティを使用することでしたが、期待どおりに動作しません。WPFでは、コントロールがユーザーに表示されているかどうかをどのように判断できますか?

コントロールがユーザーに「可視」かどうかを判断する方法はありますか?

注:私はすでにノードを崩壊更新をスキップするisExpandedとしてプロパティを使用していますが、いくつかのノードが100+の要素を持っているし、グリッドビューポートの外にあるものをスキップする方法を見つけることができません。

+5

私はかつて同様の問題を抱えていました。コントロールが表示されているかどうかを検出するコードを作成した後、実際に隠しコントロールを更新するよりも検出するコードが遅いことが判明しました。それが価値がないかもしれないので、ベンチマークしてください。 –

答えて

70

私が書いたちょっとしたヘルパー関数を使って、指定されたコンテナ内の要素がユーザーに表示されているかどうかを確認できます。要素が部分的に表示されている場合、この関数はtrueを返します。完全に表示されているかどうか確認する場合は、最後の行をrect.Contains(bounds)に置き換えます。あなたのケースでは

private bool IsUserVisible(FrameworkElement element, FrameworkElement container) 
{ 
    if (!element.IsVisible) 
     return false; 

    Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight)); 
    Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight); 
    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight); 
} 

elementは、ユーザーコントロール、およびcontainerあなたのウィンドウになります。

VirtualizingStackPanel.IsVirtualizing="True" 
VirtualizingStackPanel.VirtualizationMode="Recycling" 

をしてからご覧くださいより詳細な情報については、この

public event PropertyChangedEventHandler PropertyChanged 
    { 
     add 
     { 
      Console.WriteLine(
       "WPF is listening my property changes so I must be visible"); 
     } 
     remove 
     { 
      Console.WriteLine("WPF unsubscribed so I must be out of sight"); 
     } 
    } 

のようなあなたのデータ項目のINotifyPropertyChanged.PropertyChanged加入者に聞いてフックアップ:

+23

これは、要素がコンテナのサイズを超えている場合を考慮していません。 rect.IntersectsWith(bounds)を返す代わりに、それを修正します。 – Amanduh

+3

多くのデータでは、通常、UI仮想化を使用します。そのため、アイテムを直接設定することはしません(つまり、 'ItemsContro.Items.Add(new ...)')。データバインディングを使用します。 しかし、データオブジェクトに追加された子オブジェクト(例:ObservableList)には親がないため、データバインディングによってビジュアル階層が破損します。 'TransformToAncestor'(または' TransformToVisual')は動作しません。この場合、どうしたらいいのですか? – Shakaron

3

は含むコントロールのためにこれらのプロパティを使用し http://joew.spaces.live.com/?_c11_BlogPart_BlogPart=blogview&_c=BlogPart&partqs=cat%3DWPF

+1

初期化されたイベントは、これよりはるかに適切です。仮想化は、オブジェクトを可視化するよりもはるかに早くオブジェクトを初期化してワイヤーアップする可能性があるため、この方法ではオブジェクトが可視であるとは保証されません。 – Doug

+0

上記のリンクは壊れています。あなたは交換で更新できますか?ありがとう! –

15
public static bool IsUserVisible(this UIElement element) 
    { 
     if (!element.IsVisible) 
      return false; 
     var container = VisualTreeHelper.GetParent(element) as FrameworkElement; 
     if (container == null) throw new ArgumentNullException("container"); 

     Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.RenderSize.Width, element.RenderSize.Height)); 
     Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight); 
     return rect.IntersectsWith(bounds); 
    } 
+0

この要素は、ウィンドウが最小化されているか、他のウィンドウの後ろに隠れているために表示されませんか? – Wobbles

3

受け入れられた回答(およびこのページの他の回答)は、元のポスターが持つ特定の問題を解決しますが、タイトルに書かれた質問に適切な答えを与えません。つまり、コントロールが見えるかどうかを判断する方法ユーザー。問題は、他のコントロールでカバーされているコントロールはレンダリングすることはできますが、他のコントロールが解決しているコンテナの境界内にあるにもかかわらず、は表示されません。ユーザーによる

AAコントロールを使用すると、時々、WPFのUIElementがクリッカブル(またはPC上で到達可能なマウス)であるかどうかを判断のことができるようにする必要がユーザーに表示されるかどうかを決定する

私はときに、この問題が発生しました私はボタンがマウスでクリックできるかどうかをチェックしようとしていました。私に迷惑をかけた特別なシナリオは、ボタンが実際にユーザーに見えるが、マウスクリックを防ぐ透明レイヤー(または半透明レイヤーまたは非透明レイヤー)で覆われているということでした。そのような場合、コントロールはユーザーには見えるが、ユーザーにはアクセスできません。ユーザーには表示されないようなものです。

私は自分の解決策を考えなければなりませんでした。

EDIT - オリジナルの投稿には、InputHitTestメソッドを使用した別のソリューションがありました。しかし、それは多くの場合にうまくいかず、私はそれを再設計しなければならなかった。このソリューションははるかに堅牢で、偽の否定や否定がなくてもうまく機能しているようです。

ソリューション:

  1. は、アプリケーションのメインウィンドウ
  2. コール(左上、左下、右上、右下)、全ての角
  3. 上​​へのオブジェクトの絶対位置を取得する私たちオブジェクトを呼び出す完全にクリック可能な​​から取得したオブジェクトが元のオブジェクトまたはすべての角の視覚的な親と等しい場合は部分的に1つまたは複数のコーナーにはをクリックできます。

#1の点に注意してください。完全にクリッカブルまたは部分的に クリッカブルのここでの定義は正確ではありません - 私たちは オブジェクトのすべての四隅がクリック可能ですチェックしています。たとえば、ボタンにクリック可能なコーナーが4つありますが、 の中心にクリック可能なスポットがある場合は、それでも 完全にクリック可能と見なされます。指定されたオブジェクト内のすべての点をチェックするには、無駄な もあります。

#2の点に注意してください。私たちはそれを

を見つけること​​を希望する場合、時々(ただし、これは多くの一般的な コントロールのデフォルト値です) にオブジェクト IsHitTestVisible プロパティを設定する必要がありその後
private bool isElementClickable<T>(UIElement container, UIElement element, out bool isPartiallyClickable) 
    { 
     isPartiallyClickable = false; 
     Rect pos = GetAbsolutePlacement((FrameworkElement)container, (FrameworkElement)element); 
     bool isTopLeftClickable = GetIsPointClickable<T>(container, element, new Point(pos.TopLeft.X + 1,pos.TopLeft.Y+1)); 
     bool isBottomLeftClickable = GetIsPointClickable<T>(container, element, new Point(pos.BottomLeft.X + 1, pos.BottomLeft.Y - 1)); 
     bool isTopRightClickable = GetIsPointClickable<T>(container, element, new Point(pos.TopRight.X - 1, pos.TopRight.Y + 1)); 
     bool isBottomRightClickable = GetIsPointClickable<T>(container, element, new Point(pos.BottomRight.X - 1, pos.BottomRight.Y - 1)); 

     if (isTopLeftClickable || isBottomLeftClickable || isTopRightClickable || isBottomRightClickable) 
     { 
      isPartiallyClickable = true; 
     } 

     return isTopLeftClickable && isBottomLeftClickable && isTopRightClickable && isBottomRightClickable; // return if element is fully clickable 
    } 

    private bool GetIsPointClickable<T>(UIElement container, UIElement element, Point p) 
    { 
     DependencyObject hitTestResult = HitTest< T>(p, container); 
     if (null != hitTestResult) 
     { 
      return isElementChildOfElement(element, hitTestResult); 
     } 
     return false; 
    }    

    private DependencyObject HitTest<T>(Point p, UIElement container) 
    {      
     PointHitTestParameters parameter = new PointHitTestParameters(p); 
     DependencyObject hitTestResult = null; 

     HitTestResultCallback resultCallback = (result) => 
     { 
      UIElement elemCandidateResult = result.VisualHit as UIElement; 
      // result can be collapsed! Even though documentation indicates otherwise 
      if (null != elemCandidateResult && elemCandidateResult.Visibility == Visibility.Visible) 
      { 
       hitTestResult = result.VisualHit; 
       return HitTestResultBehavior.Stop; 
      } 

      return HitTestResultBehavior.Continue; 
     }; 

     HitTestFilterCallback filterCallBack = (potentialHitTestTarget) => 
     { 
      if (potentialHitTestTarget is T) 
      { 
       hitTestResult = potentialHitTestTarget; 
       return HitTestFilterBehavior.Stop; 
      } 

      return HitTestFilterBehavior.Continue; 
     }; 

     VisualTreeHelper.HitTest(container, filterCallBack, resultCallback, parameter); 
     return hitTestResult; 
    }   

    private bool isElementChildOfElement(DependencyObject child, DependencyObject parent) 
    { 
     if (child.GetHashCode() == parent.GetHashCode()) 
      return true; 
     IEnumerable<DependencyObject> elemList = FindVisualChildren<DependencyObject>((DependencyObject)parent); 
     foreach (DependencyObject obj in elemList) 
     { 
      if (obj.GetHashCode() == child.GetHashCode()) 
       return true; 
     } 
     return false; 
    } 

    private IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject 
    { 
     if (depObj != null) 
     { 
      for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) 
      { 
       DependencyObject child = VisualTreeHelper.GetChild(depObj, i); 
       if (child != null && child is T) 
       { 
        yield return (T)child; 
       } 

       foreach (T childOfChild in FindVisualChildren<T>(child)) 
       { 
        yield return childOfChild; 
       } 
      } 
     } 
    } 

    private Rect GetAbsolutePlacement(FrameworkElement container, FrameworkElement element, bool relativeToScreen = false) 
    { 
     var absolutePos = element.PointToScreen(new System.Windows.Point(0, 0)); 
     if (relativeToScreen) 
     { 
      return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight); 
     } 
     var posMW = container.PointToScreen(new System.Windows.Point(0, 0)); 
     absolutePos = new System.Windows.Point(absolutePos.X - posMW.X, absolutePos.Y - posMW.Y); 
     return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight); 
    } 

(例えば)ボタンがクリック可能であるかどうかを確認するために必要なことすべてを呼び出すことです:

if (isElementClickable<Button>(Application.Current.MainWindow, myButton, out isPartiallyClickable)) 
{ 
     // Whatever 
} 
+1

これを試してみたいですが、GetAbsolutePlacement()とFindVisualChildren()への参照がないようです。私は何が欠けていますか? –

+0

おっと!以前の編集でこれらのメソッドを誤って削除しましたが、戻ってしまいました。ありがとう! –

関連する問題