8

何らかのイベントで ContentTemplate を変更したい ContentControl があります。ContentTemplate のコントロールが読み込まれたときに、いくつかの値 (TextBox へのテキスト) を追加したいと考えています。しかし、プロパティ ContentTemplate を変更した後、新しい ContentTemplate が直接ではなく (新しいテンプレートのすべてのコントロールをロードするという点で) 適用されることを発見しました。

myContentControl.ContentTemplate = newContentTemplate;
// at this line controls of new template are not loaded!

その行の後にこのコードを追加してテストしました:

var cp = GetVisualChild<ContentPresenter>(myContentControl);
var txt = myContentControl.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
txt.Text = "test";

GetVisualChild

private T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        T child = default(T);
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }

エラーが発生しました:

この操作は、このテンプレートが適用されている要素に対してのみ有効です。

新しい ContentTemplate が完全に適用されたことを示すイベントはありますか?

編集1

@eran私はonApplyTemplateを試しました

public override void OnApplyTemplate()
{
   var cp = GetVisualChild<ContentPresenter>(Content_Option);
   var txt = Content_Option.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
   txt.Text = "test";
}

しかし、エラーが発生しました:

オブジェクト参照がオブジェクト インスタンスに設定されていません。

編集2

この「ダーティ」メソッドは問題なく機能します。

myContentControl.ContentTemplate = newContentTemplate;

System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(0.000001);
timer.Tick += new EventHandler(delegate(object s, EventArgs a)
{
   timer.Stop();
   var cp = GetVisualChild<ContentPresenter>(Content_Option);
   var txt = Content_Option.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
   txt.Text = "teSt";
});
timer.Start();

誰かがより「クリーンな」(プロフェッショナルな)方法で同じ結果を達成するのを手伝ってくれませんか:)

編集3

私のシナリオは、メニューとして TreeView (左側) を持ち、ContentControl の表示として Grid (右側) を持っています。TreeView にはいくつかのノードがあります。各ノードには独自の DataTemplate があります。TreeView ノードがクリックされるたびに、DataTemplate が ContentControl に設定され、値 (例: Path_Cover.Text) がデータベースから設定されます。レイアウトはほぼ Windows エクスプローラーに似ています。

さて、これはすべて必要なコードです:

XAML

    <UserControl.Resources>

      <DataTemplate x:Key="General">
        <StackPanel>
           <DockPanel>
               <TextBlock Text="Cover"/>
               <TextBox Name="Path_Cover"/>
           </DockPanel>
           <DockPanel>
               <TextBlock Text="Slide"/>
               <TextBox Name="Path_Slide"/>
           </DockPanel>
        </StackPanel>
      </DataTemplate>

      <DataTemplate x:Key="Appearance">
        <StackPanel>
           <DockPanel>
               <TextBlock Text="Cover"/>
               <TextBox Name="Path_Cover"/>
           </DockPanel>
           <DockPanel>
               <Button Content="Get Theme"/>
               <TextBox Name="Txt_Theme"/>
           </DockPanel>
        </StackPanel>
      </DataTemplate>

    <UserControl.REsources>

<Grid>
    <ContentControl Name="myContentControl"/>
</Grid>

コードビハインド

private void TreeMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
   myContentControl.ContentTemplate =(DataTemplate)this.Resources[Tree_Menu.SelectedItem.ToString()];

   System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
   timer.Interval = TimeSpan.FromMilliseconds(0.000001);
   timer.Tick += new EventHandler(delegate(object s, EventArgs a)
   {
      timer.Stop();
      switch (Tree_Menu.SelectedItem.ToString())
      {
         case "General": 
               var cp = GetVisualChild<ContentPresenter>(Content_Option);
               var txt = Content_Option.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
               txt.Text = "test";

               txt = Content_Option.ContentTemplate.FindName("Path_Slide", cp) as TextBox;
               txt.Text = "test";
               break;

        case "Appearance": 
               var cp = GetVisualChild<ContentPresenter>(Content_Option);
               var txt = Content_Option.ContentTemplate.FindName("Txt_Theme", cp) as TextBox;
               txt.Text = "test";
               break;
      }
   });
   timer.Start();
}

timer.tick イベント ハンドラー内のコードを、DataTemplate/ContentTemplate が完全に適用された後に発生する新しいイベントに "移動" する必要があります。

4

3 に答える 3

5

これはかなり古い質問であることは承知していますが、これに対する答えを探していて、それを発明したので、これはそれを共有するのに適した場所だと思いました.

標準の ContentPresenter から拡張する独自の ContentPresenter クラスを作成しただけです。

public class ContentPresenter : System.Windows.Controls.ContentPresenter {

    #region RE: ContentChanged
    public static RoutedEvent ContentChangedEvent = EventManager.RegisterRoutedEvent("ContentChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ContentPresenter));
    public event RoutedEventHandler ContentChanged {
        add { AddHandler(ContentChangedEvent, value); }
        remove { RemoveHandler(ContentChangedEvent, value); }
    }
    public static void AddContentChangedHandler(UIElement el, RoutedEventHandler handler) {
        el.AddHandler(ContentChangedEvent, handler);
    }
    public static void RemoveContentChangedHandler(UIElement el, RoutedEventHandler handler) {
        el.RemoveHandler(ContentChangedEvent, handler);
    }
    #endregion

    protected override void OnVisualChildrenChanged(System.Windows.DependencyObject visualAdded, System.Windows.DependencyObject visualRemoved) {
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);
        RaiseEvent(new RoutedEventArgs(ContentChangedEvent, this));
    }
}

これが、ContentPresenter の設計におけるこの明白な見落としに対する簡単な解決策を探している人々の助けになることを願っています.

于 2013-06-18T20:13:40.577 に答える
0

一般的に、私はその種のイベントを知りません。ただし、シナリオの典型的な WPF の方法は次のとおりです。

<ContentControl Name=myContentControl>
<ContentControl.ContentTemplate>
    <DataTemplate>
        <StackPanel>
            ...other controls here
            <TextBox Text={Binding Mode=TwoWay}/>
            ... more controls here
        </StackPanel>
    </DataTemplate>
</ContentControl.ContentTemplate>

コードビハインド:

myContentControl.Content = "Test";

または、コンテンツを ViewModel (のプロパティ) にバインドし、そこにコードを配置することもできます。

contenttemplate 内のコントロールにアクセスする場合は、名前を付けて、contenttemplate が適用されているコントロールから FindName を実行するだけです。その VisualChild のもので contentpresenter を検索する必要はありません。

コントロール テンプレートとデータ テンプレート (コンテンツ テンプレート、アイテム テンプレート) を混同しているように感じます。

  1. OnApplyTemplate は、ControlTemplate が適用された瞬間を参照するため、ContentTemplate やその他のデータ テンプレートは参照されません。
  2. ContentPresenter は、ContentTemplate ではなく、ControlTemplate の典型的な構成要素です。
于 2012-11-22T07:58:34.757 に答える