3

Excelプラグインとして実行されるWPFアプリケーションがあり、そのようなビジュアルツリーがあります

  • エクセル
    • 要素ホスト
      • WPF ユーザー コントロール
        • WPF リボン バー コントロール

プラグインが Excel 内に読み込まれると、WPF リボン バー コントロールにあるコントロールは有効になりません。以下のエラーを参照してください

System.Windows.Data Error: 4 : Cannot find source for binding with 
reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=IsActive; DataItem=null; target element 
is 'Ribbon' (Name=''); target property is 'NoTarget' (type 'Object')

リボン バー コントロールをスタンドアロン ウィンドウ (Excel の外部) にネストすると、正常に動作します。

ウィンドウの FindAncestor 呼び出しをインターセプトし、それを別のものに接続する方法はありますか? 上記のバインディングは私のコントロールではないため、変更できないことに注意してください。

4

3 に答える 3

2

一番直接的な答え

FindAncestor は WPF によって内部的に処理され、他の場所に移動する前に可能な限りビジュアル ツリーを検索します。ビジュアルの親を持たないビジュアルに到達した場合にのみ、他の場所を検索します。これは、到達した内容によって異なります。たとえば、FrameworkContentElement にヒットすると、ドキュメントのコンテナーに移動できます。残念ながら、ビジュアル ツリーの最上位が ElementHost の場合は停止するため、呼び出しを再ルーティングする方法はありません。

これは、バインディングを置き換えることが最も簡単なオプションであることを意味します。幸いなことに、これはそれほど難しくありません。

バインディングを自動的に置き換える方法

これは、ビジュアル ツリーを検索し、updateFunction の指示に従ってバインドを置き換える、以前に書いた簡単なメソッドです。updateFunction が渡されたバインディングとは異なるバインディングを返す場合、バインディングは更新されます。

static void UpdateBindings(Visual visual, Func<Binding, Binding> updateFunction)
{
  if(visual==null) return;
  for(int i=0; i<VisualTreeHelper.GetChildrenCount(visual); i++)
    UpdateBindings(VisualTreeHelper.GetChild(visual, i) as Visual, updateFunction);
  for(var enumerator = visual.GetLocalValueEnumerator(); enumerator.MoveNext(); )
  {
    var property = enumerator.Current.Property;
    var binding = BindingOperations.GetBinding(visual, property);
    if(binding==null) continue;
    var newBinding = updateFunction(binding);
    if(newBinding!=binding)
      BindingOperations.SetBinding(visual, property, newBinding);
  }
}

これがどのように機能するかを説明するために、次のように、すべての RelativeSource FindAncestor インスタンスで特定の AncestorType を置き換えるメソッドを作成する方法を次に示します。

static void ReplaceFindAncestorType(Visual visual, Type fromType, Type toType)
{
  UpdateBindings(visual, binding =>
    binding.RelativeSource.Mode != RelativeSourceMode.FindAncestor ? binding :
    binding.RelativeSource.AncestorType != fromType ? binding :
    new Binding
    {
      RelativeSource = new RelativeSource(
        RelativeSourceMode.FindAncestor,
        toType,
        binding.RelativeSource.AncestorLevel),
      Path = binding.Path,
      Mode = binding.Mode,
      Converter = binding.Converter,
      StringFormat = binding.StringFormat,
      UpdateSourceTrigger = binding.UpdateSourceTrigger,
    });
}

一般的に使用されるプロパティのみが新しいバインディングにコピーされることに注意してください。

ReplaceFindAncestorVisualType メソッドは、次のように使用できます。

elementHost.LayoutUpdated += (obj, e) =>
{
  ReplaceFindAncestorType(elementHost, typeof(Window), typeof(ElementHost);
};

あなたの場合、この一般的な置換手法は機能しません。存在しない ElementHost の IsActive プロパティを探します。そのため、RelativeSource 以外にも変更が必要になる可能性があります。これは、実際のコードが次のようになることを意味します。

elementHost.LayoutUpdated += (obj, e) =>
{
  UpdateBindings(elementHost, binding =>
    binding.RelativeSource.AncestorType != typeof(Window) ? binding :
    new Binding
    {
      Source = ultimateContainingWindowOrOtherObjectHavingIsActiveProperty,
      Path = new PropertyPath("IsActive"), // Put property name here
    });
};

上記のコードは、すべての FindAncestor:Window バインディングが探しているものであると想定していることに注意してください。必要に応じて条件を追加できます。

代替ソリューション

別の、まったく異なる解決策があります。ボーダレス ウィンドウでコンテンツを実際にホストし、カスタム コードを追加して、このウィンドウを ElementHost の上に配置し、他のウィンドウ内にあるように見せることができます。これは、ActiveWindow、ForegroundWindow、Z オーダー、最小化状態、キーボード フォーカスなどを処理する必要があるため、思ったよりもトリッキーです。しかし、ニーズが非常に単純な場合、これは合理的な解決策になります。

于 2010-04-07T04:59:53.237 に答える
0

Excelでコントロールを使用する場合、祖先にウィンドウはありませんが、おそらくSnoopを使用してバインディングが定義されている場所を見つけ、実行時に依存関係オブジェクトを(タイプ別に)見つけて、そのプロパティのバインディング式を変更できますか?

于 2010-04-04T22:33:45.377 に答える
0

もう 1 つのオプションは、Window から祖先として継承するカスタム コントロールを追加し、それを Excel コントロールにバインドすることです。

于 2010-04-04T22:44:05.117 に答える