Как получить доступ к кнопкам внутри UserControl из xaml?

на работе у меня есть несколько страниц, каждая с кнопки в тех же местах, и с теми же свойствами. Каждая страница также имеет незначительные отличия. С этой целью мы создали шаблон userControl и поместили в него все кнопки, а затем применили этот пользовательский элемент управления ко всем страницам. Однако теперь довольно сложно получить доступ к кнопкам и изменить их из xaml каждой страницы, потому что они находятся внутри UserControl на странице..... как элегантно получить доступ к кнопкам от каждого Пейдж?

что я пробовал:

  1. В настоящее время мы привязываемся к куче свойств зависимостей. Мне не нравится эта опция, потому что у меня много кнопок, и мне нужно контролировать много свойств на этих кнопках. Результатом являются сотни свойств зависимостей и настоящий беспорядок, чтобы пробраться, когда нам нужно что-то изменить.

  2. другой метод - использовать стили. Мне нравится этот метод в целом, но потому что эти кнопки внутри другого элемента управления становится трудно модифицировать их, а шаблон только для одной кнопки, в одно время.

  3. Адам Кемп опубликовал о том, чтобы позволить пользователю просто вставить свою собственную кнопку здесь, и это метод, который я в настоящее время пытаюсь имплиментировать / модифицировать. К сожалению, у меня нет доступа к Xamarin.

шаблон is вставить, когда код работает шаблон не обновление кнопки правильно. Если я поставлю точку останова в сеттере MyButton, я увижу, что стоимостью на самом деле это пустая кнопка, а не та, которую я назначил в своем главном окне. Как это исправить?

вот несколько упрощенный код:

xaml моего шаблона UserControl:

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

код моего шаблона UserControl:

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

и теперь приложение, где я хочу использовать это:

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

и вот код:

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

Edit: в то время как я хочу удалить ненужные свойства зависимостей в шаблон userControl, я все равно хотел бы установить привязки на свойства из XAML.

6 ответов


вместо того, чтобы использовать многие свойства зависимостей, предпочитайте подход стиля. Style содержит все свойства, доступные для элемента управления Button.

Я бы создал DependencyProperty для каждого стиля кнопки в UserControl.

public partial class TemplateUserControl : UserControl
{
    public TemplateUserControl()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty FirstButtonStyleProperty = 
        DependencyProperty.Register("FirstButtonStyle", typeof (Style), typeof (TemplateUserControl));

    public Style FirstButtonStyle
    {
        get { return (Style)GetValue(FirstButtonStyleProperty); }
        set { SetValue(FirstButtonStyleProperty, value); }
    }

    public static readonly DependencyProperty SecondButtonStyleProperty =
        DependencyProperty.Register("SecondButtonStyle", typeof (Style), typeof (TemplateUserControl));

    public Style SecondButtonStyle
    {
        get { return (Style)GetValue(SecondButtonStyleProperty); }
        set { SetValue(SecondButtonStyleProperty, value); }
    }
}

а затем измените xaml для кнопок, чтобы выбрать эти стили:

<UserControl x:Class="MyApp.TemplateUserControl"
             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="200" d:DesignWidth="300"
             Background="DarkGray">
    <StackPanel>
        <Button x:Name="_button" Width="200" Height="100" 
                Style="{Binding Path=FirstButtonStyle, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
        <Button x:Name="_button2" Width="200" Height="100"
                Style="{Binding Path=SecondButtonStyle, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    </StackPanel>
</UserControl>

Теперь, когда кнопки должны быть настроены, что может быть достигнуто с помощью пользовательских стилей:

<StackPanel>
    <StackPanel.Resources>
        <!--common theme properties-->
        <Style TargetType="Button" x:Key="TemplateButtonBase">
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="Foreground" Value="Blue"/>
        </Style>

        <!--unique settings of the 1st button-->
        <!--uses common base style-->
        <Style TargetType="Button" x:Key="BFirst" BasedOn="{StaticResource TemplateButtonBase}">
            <Setter Property="Content" Value="1st"/>
        </Style>

        <Style TargetType="Button" x:Key="BSecond" BasedOn="{StaticResource TemplateButtonBase}">
            <Setter Property="Content" Value="2nd"/>
        </Style>
    </StackPanel.Resources>

    <myApp:TemplateUserControl FirstButtonStyle="{StaticResource BFirst}" 
                               SecondButtonStyle="{StaticResource BSecond}"/>
</StackPanel>

enter image description here


вы можете зарегистрировать свойство зависимостей Button на UserControlи обрабатывать инициализацию в его PropertyChangedCallback.

шаблон.код XAML.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Markup.Primitives;

namespace TemplateCode
{
    public partial class Template : UserControl
    {
        public Template()
        {
            InitializeComponent();
        }

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

        public Button Button
        {
            get { return (Button)GetValue(ButtonProperty); }
            set { SetValue(ButtonProperty, value); }
        }

        public static List<DependencyProperty> GetDependencyProperties(Object element)
        {
            List<DependencyProperty> properties = new List<DependencyProperty>();
            MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
            if (markupObject != null)
            {
                foreach (MarkupProperty mp in markupObject.Properties)
                {
                    if (mp.DependencyProperty != null)
                    {
                        properties.Add(mp.DependencyProperty);
                    }
                }
            }
            return properties;
        }

        private static void ButtonChangedCallback(object sender, DependencyPropertyChangedEventArgs args)
        {
            // Get button defined by user in MainWindow
            Button userButton     = (Button)args.NewValue;
            // Get template button in UserControl
            UserControl template  = (UserControl)sender;
            Button templateButton = (Button)template.FindName("button");
            // Get userButton props and change templateButton accordingly
            List<DependencyProperty> properties = GetDependencyProperties(userButton);
            foreach(DependencyProperty property in properties)
            {
                if (templateButton.GetValue(property) != userButton.GetValue(property))
                {
                    templateButton.SetValue(property, userButton.GetValue(property));
                }
            }
        }
    }
}

шаблон.в XAML

UserControl DataContext наследуется от родителя, нет необходимости устанавливать его явно

<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"
     Background="DarkGray">

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

файл MainWindow.в XAML

ты Button.Content вместо Button

<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">
    <Window.Resources>
        <Button x:Key="UserButton" 
                Background="Yellow" 
                Content="Actual Button"
                Width="200" 
                Height="100"
                />
    </Window.Resources>
    <Grid>
        <templateCode:Template Button="{StaticResource UserButton}"/>
    </Grid>
</Window>

EDIT - кнопка привязки.Содержание

3 способа сделать это:

1. Свойства Зависимостей

самый лучший способ. Создание UserControl DP для каждого свойства на Button, безусловно, излишне, но для тех, кого вы хотите привязать к ViewModel / MainWindow DataContext, это имеет смысл.

добавление в шаблон.код XAML.cs

public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register("Text", typeof(string), typeof(Template));

public string Text
{
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty, value); }
}

шаблон.в XAML

<UserControl x:Class="TemplateCode.Template"

     ...

     DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Button x:Name="button" Width="200" Height="100" Content="{Binding Text}"/>
    </Grid>
</UserControl>

файл MainWindow.в XAML

<Window.Resources>
    <Button x:Key="UserButton" 
            Background="Yellow" 
            Width="200" 
            Height="100"
            />
</Window.Resources>
<Grid>
    <templateCode:Template 
        Button="{StaticResource UserButton}" 
        Text="{Binding DataContext.Txt, 
                       RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>

</Grid>

или

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

значением приоритета: UserButton содержание > DP Text, поэтому установка содержимого в Resources выигрывает.

2. Создание кнопки в ViewModel

пуристам MVVM это не понравится, но вы можете использовать Binding сверстать вместо StaticResource.

файл MainWindow.в XAML

<Grid>
    <templateCode:Template 
        Button="{Binding DataContext.UserButton, 
                         RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Grid>

3. Установка привязки в коде

как вы уже заметили, ViewModel prop (например,Txt) нельзя ссылаться в Resources из-за того, что все инициализируется. Вы все еще можете сделать это в коде позже, но это становится немного грязным с ошибкой, чтобы доказать.

.Окна.Ошибка данных: 4 : не удается найти источник для связывание с reference ' RelativeSource FindAncestor, Система AncestorType='.Окна.Window', AncestorLevel= '1". BindingExpression: Path=DataContext.Txt; DataItem=null; целевой элемент является "Button" (Name="); целевое свойство - "Content" (тип "Object")

обратите внимание, что вам нужно определить полный путь на Content свойство (параметр DataContext на родителей не делать.)

файл MainWindow.в XAML

<Window.Resources>
    <Button x:Key="UserButton" 
            Background="Yellow" 
            Width="200" 
            Height="100"
            Content="{Binding DataContext.Txt, 
                              RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
            />
</Window.Resources>
<Grid>
    <templateCode:Template Button="{StaticResource UserButton}"/>
</Grid>

шаблон.код XAML.cs

private static void ButtonChangedCallback(object sender, DependencyPropertyChangedEventArgs args)
{
    // Get button defined by user in MainWindow
    Button userButton = (Button)args.NewValue;
    // Get template button in UserControl
    UserControl template = (UserControl)sender;
    Button templateButton = (Button)template.FindName("button");
    // Get userButton props and change templateButton accordingly
    List<DependencyProperty> properties = GetDependencyProperties(userButton);
    foreach (DependencyProperty property in properties)
    {
    if (templateButton.GetValue(property) != userButton.GetValue(property))
        templateButton.SetValue(property, userButton.GetValue(property));
    }
    // Set Content binding
    BindingExpression bindingExpression = userButton.GetBindingExpression(Button.ContentProperty);
    if (bindingExpression != null)
        templateButton.SetBinding(Button.ContentProperty, bindingExpression.ParentBinding);
}

Если вы можете сгруппировать изменения кнопок в одно или несколько свойств в datacontext, вы можете работать с DataTriggers:

<Button x:Name="TestButton">
    <Button.Style>
        <Style>
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsButtonEnabled}" Value="True">
                    <Setter TargetName="TestButton" Property="Background" Value="Red" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

вы даже можете использовать несколько условий с MultiDataTriggers.


основная проблема заключается в том, что компоненты шаблона инициализируются перед компонентами mainwindow.Я имею в виду, что все свойства кнопки в mainwindow устанавливаются после инициализации кнопки в классе шаблона. Поэтому, как вы сказали, value устанавливает значение null. Все, что я хочу сказать, - это последовательность инициализации объектов.Если вы делаете трюк, таким образом, как следует ;

public partial class Template : UserControl
{
    private Button _btn ;

    public Template()
    {

    }

    public Button MyButton
    {
        get
        {
            return _button;
        }
        set
        {
            _btn = value;
            _button = value;
        }
    }
    protected override void OnInitialized(EventArgs e)
    {
        InitializeComponent();
        base.OnInitialized(e);

        this._button.Content = _btn.Content;
        this._button.Background = _btn.Background;
        this.Width = _btn.Width;
        this.Height = _btn.Height;
    }
}

это будет работать несомненно.


другой вариант, основанный на ответе @Funk, - сделать элемент управления содержимым вместо кнопки на шаблоне, а затем привязать содержимое элемента управления содержимым к вашему ButtonProperty в коде:

в шаблоне:

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

в коде шаблона за:

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

и затем в главном окне:

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

самое приятное в этом то, что Visual Studio достаточно умна, чтобы показать этот код во время разработки, а также иметь меньше кода в общем и целом.

вы можете установить постоянные вещи (например, местоположение, шрифт и окраску) для вашей кнопки либо на элементе управления содержимым, либо в стиле по умолчанию, а затем изменить только те части, которые вам нужны для кнопки.


один из вариантов-просто начать писать C# на странице xaml, используя

в главном окне.xaml вы меняете на:

<templateCode:Template x:Name="test">
    <x:Code><![CDATA[
        Void OnStartup()
        {
            test.MyButton.Content="Actual Button";
            test.MyButton.Background = new SolidColorBrush(Color.FromArgb(255,255,255,0));
        }
        ]]>
    </x:Code>

затем сразу после инициализации Object () вы вызываете OnStartup ().

хотя это позволяет редактировать определенные свойства в xaml, это примерно то же самое, что просто писать код в коде позади, где другие ожидают его.