Search code examples
c#wpfxamluser-controlsnested-attributes

How do I Access Buttons inside a UserControl from xaml?


At work I have several pages, each with buttons in the same places, and with the same properties. Each page also has minor differences. To that end, we created a userControl Template and put all the buttons in it, then applied that user control to all the pages. However, now it's rather hard to access the buttons and modify them from each page's xaml, because they are inside a UserControl on the page..... How do I elegantly access the buttons from each page?

What I've tried:

  1. Currently, we bind to a bunch of dependency properties. I don't like this option because I have a lot of buttons, and need to control a lot of properties on those buttons. The result is hundreds of dependency properties, and a real mess to wade through when we need to change something.

  2. Another method is to use styles. I like this method generally, but because these buttons are inside another control it becomes difficult to modify them, and the template would only be exactly right for one button, at one time.

  3. Adam Kemp posted about letting the user just insert their own button here, and this is the method I'm currently trying to impliment / modify. Unfortunately, I don't have access to Xamarin.

Although the template is inserted when the code runs, the template is not updating the button correctly. If I put a breakpoint in the MyButton Setter, I can see that value is actually an empty button, rather than the one I assigned in my main window. How do I fix this?

Here's some simplified Code:

My Template UserControl's xaml:

<UserControl x:Class="TemplateCode.Template"
     x:Name="TemplatePage"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     mc:Ignorable="d"
     d:DesignHeight="350"
     d:DesignWidth="525"
     DataContext="{Binding RelativeSource={RelativeSource Self}}"
     Background="DarkGray">

     <Grid>
          <Button x:Name="_button" Width="200" Height="100" Content="Template Button"/>
     </Grid>
</UserControl>

My Template UserControl's Code Behind:

using System.Windows.Controls;

namespace TemplateCode
{
    public partial class Template : UserControl
    {
        public static Button DefaultButton;

        public Template()
        {
             InitializeComponent();
        }

        public Button MyButton
        {
            get
            {
                 return _button;
            }
            set
            {
                _button = value; //I get here, but value is a blank button?!

                // Eventually, I'd like to do something like:
                // Foreach (property in value) 
                // {
                //     If( value.property != DefaultButton.property) )
                //     {
                //         _button.property = value.property;
                //     }
                // }
                // This way users only have to update some of the properties
            }
        }
    }
}

And now the application where I want to use it:

<Window x:Class="TemplateCode.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"

    xmlns:templateCode="clr-namespace:TemplateCode"

    Title="MainWindow"
    Height="350"
    Width="525"
    Background="LimeGreen"
    DataContext="{Binding RelativeSource={RelativeSource Self}}" >

    <Grid>
        <templateCode:Template>
            <templateCode:Template.MyButton>

                <Button Background="Yellow" 
                    Content="Actual Button"
                    Width="200" 
                    Height="100"/>

            </templateCode:Template.MyButton>
        </templateCode:Template>
    </Grid>
</Window>

And Now the Code Behind:

Using System.Windows;
Namespace TemplateCode
{
    Public partial class MainWindow : Window
    {
        Public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Edit: While I want to remove unnecessary dependency properties in the template userControl, I'd still like to set bindings on the button's properties from the XAML.


Solution

  • Another Option based on @Funk's answer is to make a content control instead of a button on the template, then bind the content control's content to your ButtonProperty in the code behind:

    on the template:

    <ContentControl Content={Binding myButton} Width="200" Height="100"/>
    

    in the template code behind:

    public static readonly DependencyProperty myButtonProperty =
            DependencyProperty.Register("Button", typeof(Button), typeof(Template),
                new UIPropertyMetadata(new PropertyChangedCallback(ButtonChangedCallback)));
    

    and then on the Main Window:

    <Window.Resources>
        <Button x:Key="UserButton" 
                Background="Yellow" 
                Content="Actual Button"
                />
    </Window.Resources>
    <Grid>
        <templateCode:Template myButton="{StaticResource UserButton}"/>
    </Grid>
    

    The nice thing about this is that Visual Studio is smart enough to show this code at design time, as well as having less code overall.

    You can set things constant things (like location, font, and coloring) for your button either on the content control or in a default style, and then modify just the parts you need for you button.