C# 如何从xaml访问UserControl中的按钮?

C# 如何从xaml访问UserControl中的按钮?,c#,wpf,xaml,user-controls,nested-attributes,C#,Wpf,Xaml,User Controls,Nested Attributes,在工作中,我有几个页面,每个页面的按钮都位于相同的位置,并且具有相同的属性。每一页也有细微的差别。为此,我们创建了一个userControl模板,并将所有按钮放入其中,然后将该用户控件应用于所有页面。但是,现在很难从每个页面的xaml访问和修改按钮,因为它们位于页面上的UserControl中如何优雅地访问每页的按钮? 我所尝试的: 目前,我们绑定到一组依赖属性。我不喜欢这个选项,因为我有很多按钮,需要控制这些按钮上的很多属性。结果是成百上千的依赖性属性,当我们需要改变某些东西时,这真是一团糟

在工作中,我有几个页面,每个页面的按钮都位于相同的位置,并且具有相同的属性。每一页也有细微的差别。为此,我们创建了一个userControl模板,并将所有按钮放入其中,然后将该用户控件应用于所有页面。但是,现在很难从每个页面的xaml访问和修改按钮,因为它们位于页面上的UserControl中如何优雅地访问每页的按钮?

我所尝试的:

  • 目前,我们绑定到一组依赖属性。我不喜欢这个选项,因为我有很多按钮,需要控制这些按钮上的很多属性。结果是成百上千的依赖性属性,当我们需要改变某些东西时,这真是一团糟

  • 另一种方法是使用样式。我通常喜欢这种方法,但由于这些按钮位于另一个控件中,因此很难修改它们,而且模板一次只能用于一个按钮

  • Adam Kemp发布了关于让用户只插入他们自己的按钮的帖子,这就是我目前正在尝试实现/修改的方法。不幸的是,我无法访问Xamarin

  • 尽管在代码运行时插入了模板,但模板没有正确更新按钮。如果我在MyButton Setter中放置断点,我可以看到value实际上是一个空按钮,而不是我在主窗口中指定的按钮。我该如何解决这个问题

    下面是一些简化的代码:

    我的模板UserControl的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>
    
    现在是我要使用它的应用程序:

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

    编辑:虽然我想删除模板userControl中不必要的依赖属性,但我仍然想从XAML中设置按钮属性的绑定。

    如果可以将对按钮的更改分组到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中使用多个条件。

    一个选项是使用

    在xaml页面上开始编写C![CDATA[***]>

    在Main Window.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中编辑特定属性,但这与在代码中编写代码的方式大致相同,其他人都希望这样做。

    您可以在
    UserControl
    上注册依赖属性
    按钮,并在其
    属性ChangedCallback
    中处理初始化

    Template.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));
                    }
                }
            }
        }
    }
    
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(Template));
    
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
    
    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);
    }
    
    main window.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>
    
    <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>
    
    <Grid>
        <templateCode:Template 
            Button="{Binding DataContext.UserButton, 
                             RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
    </Grid>
    
    <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>
    
    您正在设置
    按钮。内容
    而不是
    按钮

    <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>
    
    模板.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>
    
    <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>
    
    <Grid>
        <templateCode:Template 
            Button="{Binding DataContext.UserButton, 
                             RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
    </Grid>
    
    <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>
    
    3。在代码中设置绑定

    正如您已经注意到的,ViewModel道具(例如,
    Txt
    )不能在
    参考资料中引用,因为所有内容都已初始化。以后您仍然可以在代码中完成这项工作,但要证明的错误有点混乱

    System.Windows.Data错误:4:找不到与绑定的源 参考“相对资源查找器”, AncestorType='System.Windows.Window',AncestorLevel='1'。 BindingExpression:Path=DataContext.Txt;DataItem=null;目标元素 是“按钮”(名称=“”);目标属性为“内容”(类型为“对象”)

    注意:您需要在
    Content
    属性上定义完整路径(在父级上设置
    DataContext
    不行)

    main window.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>
    
    <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>
    
    <Grid>
        <templateCode:Template 
            Button="{Binding DataContext.UserButton, 
                             RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
    </Grid>
    
    <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>
    
    
    
    Template.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));
                    }
                }
            }
        }
    }
    
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(Template));
    
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
    
    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);
    }
    
    private static void按钮ChangedCallback(对象发送方,DependencyPropertyChangedEventArgs参数)
    {
    //获取用户在主窗口中定义的按钮
    Button userButton=(Button)args.NewValue;
    //在UserControl中获取模板按钮
    UserControl模板=(UserControl)发送方;
    Button templateButton=(Button)template.FindName(“Button”);
    //获取userButton道具并相应地更改templateButton
    列表属性=GetDependencyProperties(userButton);
    foreach(属性中的DependencyProperty属性)
    {
    if(templateButton.GetValue(属性)!=userButton.GetValue(属性))
    SetValue(属性,userButton.GetValue(属性));
    }
    //设置内容绑定
    BindingExpression=userButton.GetBindingExpression(Button.ContentProperty);
    if(bindingExpression!=null)
    templateButton.SetBinding(Button.ContentProperty、bindingExpression.ParentBinding);
    }
    
    与其使用许多依赖属性,不如选择样式方法。样式包含按钮控件可用的每个属性

    我将为UserControl中的每个按钮样式创建DependencyProperty

    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>
    
    
    

    主要问题是模板组件在主窗口组件之前初始化。我的意思是,主窗口中按钮的所有属性都是在模板类中的按钮初始化之后设置的。因此,正如您所说,该值设置为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答案的选项是创建一个内容控件,而不是模板上的按钮,然后将内容控件的内容绑定到您的B