2012-11-21 5 views
7

Mam ContentControl, który chcę zmienić to ContentTemplate w niektórych przypadkach. Chcę dodać niektóre wartości (tekst do TextBox) po załadowaniu kontroli w ContentTemplate. Ale odkryłem, że nowy ContentTemplate jest stosowany (pod względem ładowania wszystkich kontrolek nowego szablonu) NIE BEZPOŚREDNIO po zmianie właściwości ContentTemplate.Czy jest jakieś zdarzenie wskazujące, że nowy ContentTemplate jest w pełni zastosowany?

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

testowałem przez dodanej tym kodem po tej linii:

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; 
    } 

Mam błąd:

This operation is valid only on elements that have this template applied.

Czy istnieje jakiś wydarzenie pokazując nowy ContentTemplat e jest całkowicie zastosowane?

EDIT 1

@eran Próbowałem 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"; 
} 

ale mam błąd:

Object reference not set to an instance of an object.

EDIT 2

to "brudne" metoda działa dobrze:

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(); 

może ktoś mi pomóc osiągnąć ten sam rezultat z bardziej "czysty" (profesional) sposób :)

EDIT 3

My Scenariusz: Mam TreeView (po lewej stronie) jako menu i siatkę (po prawej stronie) jako wyświetlacz dla ContentControl. TreeView ma kilka węzłów. Każdy węzeł ma swój własny DataTemplate. Za każdym razem, gdy kliknięty jest węzeł TreeView, DataTemplate jest ustawiona na ContentControl, a wartość (np. Path_Cover.Text) jest ustawiana z bazy danych. Układ mniej więcej przypominający Eksplorator Windows.

Cóż, to wszystko jest niezbędne kod:

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> 

Code Behind

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(); 
} 

mi wystarczy "przenieść" kod wewnątrz timera .tick event handler do jakiegoś nowego zdarzenia, które po aplikacji DataTemplate/ContentTemplate zostało całkowicie zastosowane.

+0

OnLoad lub OnLoadData jest wywoływana po OnApplayTemplate check msdn –

+0

@eran Zdarzenie Onload jest uruchamiane tylko jeden raz. Chcę zdarzenia, które będzie uruchamiane za każdym razem, gdy zmienię ContentTemplate. – Reyn

Odpowiedz

0

Generalnie nie znam żadnych wydarzeń tego rodzaju.Ale typowy sposób WPF za scenariusz jest taki:

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

Kod za:

myContentControl.Content = "Test"; 

Albo można powiązać Treści (propperty z) ViewModel i umieścić kod w tam.

Jeśli chcesz dostać się do kontrolki wewnątrz contenttemplate, po prostu nadaj jej nazwę i wykonaj FindName z formantu, do którego zastosowany jest contenttemplate. Nie ma potrzeby, aby wyszukiwane było coś z prezenterem treści VisualChild.

Mam przeczucie, że miksujesz kontrolne szablony i datatemplates (contenttemplate, itemtemplate).

  1. OnApplyTemplate odnosi się do momentu zastosowania ControlTemplate, więc nie do ContentTemplate ani żadnego innego datatemplate.
  2. ContentPresenters są typowym składnikiem ControlTemplates, a nie ContentTemplates.
+0

Przykro mi, ale jestem bardzo nowy w WPF. Jest tyle rzeczy, których muszę się najpierw nauczyć (łącznie z koncepcją mvvm). Mógłbyś po prostu "podążać" za powyższym kodem i podać mi prostą odpowiedź (kod) w oparciu o mój przepływ: P Po prostu potrzebuję "przenieść" kod wewnątrz programu obsługi zdarzeń timer.tick do jakiegoś nowego zdarzenia. – Reyn

+0

... i tak, czasami jestem zdezorientowany danymi DataTemplate, ContentTemplate, ItemTemplate i ContentControl. : P – Reyn

+0

Mam wrażenie, że chcesz osiągnąć coś prostego, ale myślisz w skomplikowanych rozwiązaniach. Nie ma potrzeby przechodzenia przez drzewo wizualne dla prezentera treści. Więc nie potrzebujesz timera. Proste: użyj dwóch modułów kontroli treści z zawartością zdefiniowaną w szablonach i zmień widoczność na wybraną. Zacznij od tego i spróbuj stworzyć lepsze rozwiązanie. – Grafix

1

Jestem świadomy, że jest to dość stare pytanie, ale szukałem odpowiedzi na to i wymyśliłem jedną, uważam, że będzie to dobre miejsce do jej udostępnienia.

ja po prostu stworzył własną rękę klasy ContentPresenter rozciągający się od standardowego 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)); 
    } 
} 

Mam nadzieję, że to może pomóc ci z was tam szukasz prostego rozwiązania tego jaskrawym nadzoru w projektowaniu ContentPresenter.

+0

Dzięki, działa idealnie! – BendEg

0

Nie sądzę, aby takie zdarzenie miało miejsce w środowisku WPF. Możesz jednak upewnić się, że kod jest uruchamiany po zastosowaniu nowo przypisanego szablonu treści.

Sposobem na osiągnięcie tego (i "właściwej" wersji "brudnego" rozwiązania) jest użycie Dispatcher powiązanego z Twoim ContentControl. Kod ten będzie robić tylko to, co starasz się osiągnąć:

myContentControl.ContentTemplate = newContentTemplate; 
myContentControl.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => 
{ 
    var cp = GetVisualChild<ContentPresenter>(myContentControl); 
    var txt = myContentControl.ContentTemplate.FindName("Path_Cover", cp) as TextBox; 
    txt.Text = "test"; 
})); 

Zauważ, że priorytetem, z którym ten kod będzie wykonywany jest ustawiony na DispatcherPriority.Loaded, więc zostanie on wykonany z tego samego priorytetu, jak można umieścić kod w module obsługi zdarzenia FrameworkElement.Loaded.

Powiązane problemy