Nick Banks
Nick Banks

Reputation: 4408

Can't Find Element in WinRT XAML

I have the following CustomControl that I want to use as a base for a Banner:

XAML:

<UserControl x:Name="userControl"
    x:Class="Nova.WinRT.Controls.BannerPanel"
    ...>

    <Grid Background="#BF8B8B8B">
        <Grid Height="400" Background="#FF050A7C" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="50"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="50"/>
            </Grid.RowDefinitions>
            <TextBlock x:Name="_Title" Text="Title" FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            <ContentPresenter x:Name="_Content" Grid.Row="1"/>
            <StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
                <Button x:Name="_OK" Content="OK" HorizontalAlignment="Center" Click="OkPressed" FontSize="18" Width="100" Margin="20,0" />
                <Button x:Name="_Cancel" Content="Cancel" HorizontalAlignment="Center" Click="CancelPressed" FontSize="18" Width="100" Margin="20,0" />
            </StackPanel>
        </Grid>
    </Grid>
</UserControl>

C#:

public sealed partial class BannerPanel : UserControl
{
    /// <summary>
    /// Title of the Banner
    /// </summary>
    public string Title
    {
        get { return _Title.Text; }
        set { _Title.Text = value; }
    }

    /// <summary>
    /// The visibility of the OK button
    /// </summary>
    public Visibility OKVisibility
    {
        get { return _OK.Visibility; }
        set { _OK.Visibility = value; }
    }

    /// <summary>
    /// The visibility of the Cancel button
    /// </summary>
    public Visibility CancelVisibility 
    {
        get { return _Cancel.Visibility; }
        set { _Cancel.Visibility = value; }
    }

    /// <summary>
    /// The inner content of the panel
    /// </summary>
    public FrameworkElement InnerContent 
    {
        get { return (FrameworkElement)_Content.Content; }
        set { _Content.Content = value; }
    }

    /// <summary>
    /// Fires when the Ok button is clicked
    /// </summary>
    public event RoutedEventHandler OkClick;

    /// <summary>
    /// Fires when the Cancel button is clicked
    /// </summary>
    public event RoutedEventHandler CancelClick;
};

But when I use it (see the following XAML), the autogenerated code does not find the elements in the inner content:

XAML:

<Controls:BannerPanel x:Name="Banner" Title="Terms and Policy" CancelVisibility="Collapsed" OkClick="OnTermsAccepted">
    <Controls:BannerPanel.InnerContent>
        <ScrollViewer Width="500">
            <TextBlock x:Name="TermsText" TextWrapping="Wrap" FontSize="12" />
        </ScrollViewer>
    </Controls:BannerPanel.InnerContent>
</Controls:BannerPanel>

C#:

public sealed partial class TermsBanner : UserControl
{
    /// <summary>
    /// Constructor
    /// </summary>
    public TermsBanner()
    {
        this.InitializeComponent();

        // Why do I have to find TermsText Manually like this??????
        TermsText = (Banner.InnerContent as ScrollViewer).Content as TextBlock;
        TermsText.Text = TermsOfUse;
    }
};

Why do I have to manually point the variable TermsText to the correct thing? Why can't it automatically find it with FindName() like it usually does?

Upvotes: 0

Views: 1695

Answers (1)

Filip Skakun
Filip Skakun

Reputation: 31724

  1. You should define DependencyProperty properties instead of regular ones.
  2. Derive from ContentControl instead of UserControl.
  3. Put your XAML in Themes/Generic.xaml in a Style/Setter/Property="Template" Value=".... It happens automatically if you create the control using the Templated Control VS item template.
  4. Use TemplateBinding to bind properties of inner template elements to your control properties.

FindName might not work across namescopes. I never use it.

excuse the typos. Typed on a phone.

Here's a solution:

Created templated control (also known us custom control - not to be confused with a UserControl) and modified it to derive from ContentControl instead of Control:

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Templated Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234235

namespace App124
{
    [TemplatePart(Name = "_OK", Type = typeof(Button))]
    [TemplatePart(Name = "_Cancel", Type = typeof(Button))]
    public sealed class BannerPanel : ContentControl
    {
        #region Title
        /// <summary>
        /// Title Dependency Property
        /// </summary>
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register(
                "Title",
                typeof(string),
                typeof(BannerPanel),
                new PropertyMetadata(null, OnTitleChanged));

        /// <summary>
        /// Gets or sets the Title property. This dependency property 
        /// indicates ....
        /// </summary>
        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        /// <summary>
        /// Handles changes to the Title property.
        /// </summary>
        /// <param name="d">
        /// The <see cref="DependencyObject"/> on which
        /// the property has changed value.
        /// </param>
        /// <param name="e">
        /// Event data that is issued by any event that
        /// tracks changes to the effective value of this property.
        /// </param>
        private static void OnTitleChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (BannerPanel)d;
            string oldTitle = (string)e.OldValue;
            string newTitle = target.Title;
            target.OnTitleChanged(oldTitle, newTitle);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes
        /// to the Title property.
        /// </summary>
        /// <param name="oldTitle">The old Title value</param>
        /// <param name="newTitle">The new Title value</param>
        private void OnTitleChanged(
            string oldTitle, string newTitle)
        {
        }
        #endregion

        #region OKVisibility
        /// <summary>
        /// OKVisibility Dependency Property
        /// </summary>
        public static readonly DependencyProperty OKVisibilityProperty =
            DependencyProperty.Register(
                "OKVisibility",
                typeof(Visibility),
                typeof(BannerPanel),
                new PropertyMetadata(Visibility.Visible, OnOKVisibilityChanged));

        /// <summary>
        /// Gets or sets the OKVisibility property. This dependency property 
        /// indicates ....
        /// </summary>
        public Visibility OKVisibility
        {
            get { return (Visibility)GetValue(OKVisibilityProperty); }
            set { SetValue(OKVisibilityProperty, value); }
        }

        /// <summary>
        /// Handles changes to the OKVisibility property.
        /// </summary>
        /// <param name="d">
        /// The <see cref="DependencyObject"/> on which
        /// the property has changed value.
        /// </param>
        /// <param name="e">
        /// Event data that is issued by any event that
        /// tracks changes to the effective value of this property.
        /// </param>
        private static void OnOKVisibilityChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (BannerPanel)d;
            Visibility oldOKVisibility = (Visibility)e.OldValue;
            Visibility newOKVisibility = target.OKVisibility;
            target.OnOKVisibilityChanged(oldOKVisibility, newOKVisibility);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes
        /// to the OKVisibility property.
        /// </summary>
        /// <param name="oldOKVisibility">The old OKVisibility value</param>
        /// <param name="newOKVisibility">The new OKVisibility value</param>
        private void OnOKVisibilityChanged(
            Visibility oldOKVisibility, Visibility newOKVisibility)
        {
        }
        #endregion

        #region CancelVisibility
        /// <summary>
        /// CancelVisibility Dependency Property
        /// </summary>
        public static readonly DependencyProperty CancelVisibilityProperty =
            DependencyProperty.Register(
                "CancelVisibility",
                typeof(Visibility),
                typeof(BannerPanel),
                new PropertyMetadata(Visibility.Visible, OnCancelVisibilityChanged));

        /// <summary>
        /// Gets or sets the CancelVisibility property. This dependency property 
        /// indicates ....
        /// </summary>
        public Visibility CancelVisibility
        {
            get { return (Visibility)GetValue(CancelVisibilityProperty); }
            set { SetValue(CancelVisibilityProperty, value); }
        }

        /// <summary>
        /// Handles changes to the CancelVisibility property.
        /// </summary>
        /// <param name="d">
        /// The <see cref="DependencyObject"/> on which
        /// the property has changed value.
        /// </param>
        /// <param name="e">
        /// Event data that is issued by any event that
        /// tracks changes to the effective value of this property.
        /// </param>
        private static void OnCancelVisibilityChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (BannerPanel)d;
            Visibility oldCancelVisibility = (Visibility)e.OldValue;
            Visibility newCancelVisibility = target.CancelVisibility;
            target.OnCancelVisibilityChanged(oldCancelVisibility, newCancelVisibility);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes
        /// to the CancelVisibility property.
        /// </summary>
        /// <param name="oldCancelVisibility">The old CancelVisibility value</param>
        /// <param name="newCancelVisibility">The new CancelVisibility value</param>
        private void OnCancelVisibilityChanged(
            Visibility oldCancelVisibility, Visibility newCancelVisibility)
        {
        }
        #endregion

        /// <summary>
        /// Fires when the Ok button is clicked
        /// </summary>
        public event RoutedEventHandler OkClick;

        /// <summary>
        /// Fires when the Cancel button is clicked
        /// </summary>
        public event RoutedEventHandler CancelClick;

        public BannerPanel()
        {
            this.DefaultStyleKey = typeof(BannerPanel);
        }

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            var cancelButton = (Button)GetTemplateChild("_Cancel");
            var okButton = (Button)GetTemplateChild("_OK");
            cancelButton.Click += CancelClick;
            okButton.Click += OkClick;
        }
    }
}

Updated Themes/Generic.xaml to this:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App124">

    <Style
        TargetType="local:BannerPanel">
        <Setter
            Property="HorizontalContentAlignment"
            Value="Left" />
        <Setter
            Property="VerticalContentAlignment"
            Value="Top" />
        <Setter
            Property="Title"
            Value="Title" />
        <Setter
            Property="OKVisibility"
            Value="Visible" />
        <Setter
            Property="CancelVisibility"
            Value="Visible" />
        <Setter
            Property="Template">
            <Setter.Value>
                <ControlTemplate
                    TargetType="local:BannerPanel">
                    <Grid
                        Background="#BF8B8B8B">
                        <Grid
                            Height="400"
                            Background="#FF050A7C"
                            VerticalAlignment="Center">
                            <Grid.RowDefinitions>
                                <RowDefinition
                                    Height="50" />
                                <RowDefinition
                                    Height="*" />
                                <RowDefinition
                                    Height="50" />
                            </Grid.RowDefinitions>
                            <TextBlock
                                x:Name="_Title"
                                Text="{TemplateBinding Title}"
                                FontSize="30"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center" />
                            <ContentPresenter
                                Grid.Row="1"
                                ContentTemplate="{TemplateBinding ContentTemplate}"
                                ContentTransitions="{TemplateBinding ContentTransitions}"
                                Content="{TemplateBinding Content}"
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                Margin="{TemplateBinding Padding}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                            <StackPanel
                                Orientation="Horizontal"
                                Grid.Row="2"
                                HorizontalAlignment="Center">
                                <Button
                                    x:Name="_OK"
                                    Content="OK"
                                    Visibility="{TemplateBinding OKVisibility}"
                                    HorizontalAlignment="Center"
                                    FontSize="18"
                                    Width="100"
                                    Margin="20,0" />
                                <Button
                                    x:Name="_Cancel"
                                    Content="Cancel"
                                    Visibility="{TemplateBinding CancelVisibility}"
                                    HorizontalAlignment="Center"
                                    FontSize="18"
                                    Width="100"
                                    Margin="20,0" />
                            </StackPanel>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

This is how it's used in MainPage.xaml:

<Page
    x:Class="App124.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App124"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid
        Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <local:BannerPanel
            x:Name="Banner"
            Title="Terms and Policy"
            CancelVisibility="Collapsed"
            OkClick="OnTermsAccepted">
            <ScrollViewer
                Width="500">
                <TextBlock
                    x:Name="TermsText"
                    Text="Terms and Conditions"
                    TextWrapping="Wrap"
                    FontSize="12" />
            </ScrollViewer>
        </local:BannerPanel>
    </Grid>
</Page>

and MainPage.xaml.cs code behind:

using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace App124
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private void OnTermsAccepted(object sender, RoutedEventArgs e)
        {
            new MessageDialog(TermsText.Text).ShowAsync();
        }
    }
}

Upvotes: 4

Related Questions