none
TextBox within DataTemplate for ContentPresenter not triggering update event

    Question

  • I have designed a complex DataTemplate to re-use among several ContentPresenters I have on a dialog. Within this DataTemplate I have a TextBox that is bound to a DependencyProperty of a custom UserControl. I'm having trouble setting the binding correctly to get updates reflected in my viewmodel. Setting the TextBox text from the property value works correctly, but not the other direction, updates when the user changes the text are not updating the source. I've created a small sample program to illustrate the issue.

    MainWindow.xaml:

    <Window x:Class="TestBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:TestBinding="clr-namespace:TestBinding" 
        Title="MainWindow" 
        Height="203" Width="223">
      <Window.Resources>
        <DataTemplate x:Key="CustomUcTextBoxTemplate">
          <StackPanel>
            <Grid >
              <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
              </Grid.RowDefinitions>
              <TextBox x:Name="testTextBox" Height="28" 
                Text="{Binding Path=Title,
                   RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType=TestBinding:CustomUC}, 
                   Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                />
              <TextBox Grid.Row="1" Text=""/>
            </Grid>
          </StackPanel>
        </DataTemplate>
    
        <Style TargetType="{x:Type TestBinding:CustomUC}">
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type TestBinding:CustomUC}">
                <ContentPresenter x:Name="customCP" ContentTemplate="{StaticResource CustomUcTextBoxTemplate}" />
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
      </Window.Resources>
      <StackPanel>
        <Label >Does this work:</Label>
        <ContentPresenter x:Name="theControl" HorizontalAlignment="Stretch" Content="{Binding Path=CustomUCItem}" />
      </StackPanel>
    </Window>
    

    MainWindow.xaml.cs: (only remarkable item is setting of DataContext)

    using System.Windows;
    
    namespace TestBinding
    {
      /// <summary>
      /// Interaction logic for MainWindow.xaml
      /// </summary>
      public partial class MainWindow : Window
      {
        public MainWindow()
        {
          DataContext = new WindowViewModel();
          InitializeComponent();
    
        }
      }
    }
    

    WindowViewModel.cs:

    namespace TestBinding
    {
      class WindowViewModel
      {
        private CustomUC _customUCItem;
    
        public WindowViewModel()
        {
          CustomUCItem = new CustomUC("It works!");
        }
    
        public CustomUC CustomUCItem
        {
          get { return _customUCItem; }
          set { _customUCItem = value; }
        }
      }
    }
    

    And the custom UserControl, CustomUC.cs:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace TestBinding
    {
      class CustomUC: UserControl
      {
        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(CustomUC));
        public string Title
        {
          get { return (string)GetValue(TitleProperty); }
          set { SetValue(TitleProperty, value);
            Update();
          }
        }
    
        public CustomUC(string title)
        {
          Title = title;
        }
    
        private void Update()                    
        {
          Console.WriteLine("I Changed!");
        }
      }
    }
    

    The Update method is executed when the window is first loaded, but not when the user changes the TextBox contents. I've tried to attach a handler to the TextBox via code to force the Update method to be called, but I've had no luck getting a hold of it via code. I've tried the following, among other things:

        public override void OnApplyTemplate()
        {
          ContentPresenter cp = (ContentPresenter) (this.Template.FindName("customCP",this));
          DataTemplate dt = cp.ContentTemplate as DataTemplate;
          
          TextBox tb = dt.FindName("testTextBox", this) as TextBox;
        }

    but this comes back with the dreaded "This operation is valid only on elements that have this template applied " on the FindName.  I also tried:

          TextBox tb = dt.FindName("testTextBox", cp) as TextBox;
    

    with the same result. Frustrating as I can drill down in the debugger through the CustomUC members and see the TextBox in the XAML nodes list, in a non-public member, but I can't seem to find the right incantation to get to it.

    Help appreciated.

    Janene

     


    Janene
    • Edited by JaneneMc Thursday, October 21, 2010 8:16 PM edit screwed up format, ugh!
    Thursday, October 21, 2010 7:57 PM

Answers

  • Hi JaneneMc,

    Template.FindName method expects a valid parent which the template is applied; actually the valid templated parent for DataTemplate is ContentPresenter. You need to walk the tree of your item to locate its ContentPresenter.

    And in your case, you called the OnApplyTemplate method to find your item inside DataTemplate. Whereas this method used to build the current template's visual tree if necessary. That is, the visual tree is not generated yet at this point. For more information please refer to http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.applytemplate.aspx.

    What you should do is to postpone the implement until the visual tree in DataTemplate is finished. Say, put it in Loaded event, like below

     

      void UserControl1_Loaded(object sender, RoutedEventArgs e)
      {
       ContentPresenter cp = (ContentPresenter)(this.Template.FindName("customCP", this));
       DataTemplate dt = FindResource("CustomUcTextBoxTemplate") as DataTemplate;
       TextBox tb = (TextBox)dt.FindName("testTextBox", cp);
       tb.Text = "Oh, I got it!";
      }
    

    Hope it helps!

     

    Best regards.

    Yves


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by JaneneMc Thursday, November 4, 2010 10:08 PM
    Thursday, November 4, 2010 6:30 AM
    Moderator

All replies

  • Hi JaneneMc,

    To track object changes is for the object to raise an event when a property changes. You may achieve this with an implementation of the INotifyPropertyChanged interface. For more information please refer to http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx

    Hope it helps. If I misunderstand you please let me know.

     

    Best regards,

    Yves


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Tuesday, October 26, 2010 12:02 PM
    Moderator
  • Hi Yves, thanks for a reply. Yes, if I can get a reference to the TextBox,  my intent was to then register an event handler for KeyUp or some other event, but I can't get to the TextBox currently.

    Janene


    Janene
    Tuesday, October 26, 2010 4:01 PM
  • Carole,

    Yes that's what I'm trying to do, but the call on FindName generates the InvalidOperationException "This operation is valid only on elements that have this template applied."

    Any idea why this isn't working?

    Janene


    Janene
    Tuesday, October 26, 2010 10:34 PM
  • Hi JaneneMc,

    Template.FindName method expects a valid parent which the template is applied; actually the valid templated parent for DataTemplate is ContentPresenter. You need to walk the tree of your item to locate its ContentPresenter.

    And in your case, you called the OnApplyTemplate method to find your item inside DataTemplate. Whereas this method used to build the current template's visual tree if necessary. That is, the visual tree is not generated yet at this point. For more information please refer to http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.applytemplate.aspx.

    What you should do is to postpone the implement until the visual tree in DataTemplate is finished. Say, put it in Loaded event, like below

     

      void UserControl1_Loaded(object sender, RoutedEventArgs e)
      {
       ContentPresenter cp = (ContentPresenter)(this.Template.FindName("customCP", this));
       DataTemplate dt = FindResource("CustomUcTextBoxTemplate") as DataTemplate;
       TextBox tb = (TextBox)dt.FindName("testTextBox", cp);
       tb.Text = "Oh, I got it!";
      }
    

    Hope it helps!

     

    Best regards.

    Yves


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by JaneneMc Thursday, November 4, 2010 10:08 PM
    Thursday, November 4, 2010 6:30 AM
    Moderator
  • Ahhh, that helps a lot! Thanks, Yves!

    Janene


    Janene
    Thursday, November 4, 2010 10:08 PM