如何从 xaml 访问 UserControl 内的按钮?
How do I Access Buttons inside a UserControl from xaml?
在工作中,我有几个页面,每个页面的按钮都在相同的位置,并且具有相同的属性。每个页面也有细微差别。为此,我们创建了一个 userControl 模板并将所有按钮放入其中,然后将该用户控件应用于所有页面。但是,现在很难从每个页面的 xaml 访问按钮和修改它们,因为它们位于页面上的 UserControl 中..... 我如何从每个页面优雅地访问按钮页数?
我试过的:
目前,我们绑定了一堆依赖属性。我不喜欢这个选项,因为我有很多按钮,并且需要控制这些按钮的很多属性。结果是成百上千的依赖属性,当我们需要改变某些东西时,真的是一团糟。
另一种方法是使用样式。我一般喜欢这种方法,但是因为这些按钮在另一个控件中,所以很难修改它们,而且模板一次只能完全适合一个按钮。
Adam Kemp 发布了关于让用户只需插入他们自己的按钮 here,这是我目前正在尝试实现/修改的方法。不幸的是,我无法访问 Xamarin。
虽然在代码运行时插入了模板 ,但模板 并未 正确更新按钮。如果我在 MyButton Setter 中放置一个断点,我可以看到 value 实际上是一个空按钮,而不是我在 main window 中分配的按钮。我该如何解决这个问题?
这是一些简化的代码:
我的模板 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>
我的模板用户控件的隐藏代码:
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();
}
}
}
编辑: 虽然我想删除 template userControl 中不必要的依赖属性,但我仍然想在按钮的属性来自XAML。
如果您可以将对按钮的更改分组到数据上下文中的一个或多个属性,则可以使用 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 使用多个条件。
一种选择是使用 < ![CDATA[ *** ]]>
在 xaml 页面上简单地开始编写 C#
在 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>
然后在 Initialize Object() 之后立即调用 OnStartup()。
虽然这确实允许您编辑 xaml 中的特定属性,但这与仅在代码后面编写代码大致相同,其他人期望它是这样。
您可以在 UserControl
上注册一个依赖项 属性 Button
并在其 PropertyChangedCallback
中处理初始化。
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));
}
}
}
}
}
Template.xaml
UserControl DataContext
继承自parent,无需显式设置
<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>
编辑 - 绑定 Button.Content
3 种方法:
1.依赖属性
迄今为止最好的方法。为 Button
上的每个 属性 创建 UserControl
DP 当然是矫枉过正,但对于那些你想绑定到 ViewModel / MainWindow DataContext 的人来说这是有意义的。
加入Template.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); }
}
Template.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 道具(例如 Txt
)无法在 Resources
中引用,因为所有内容都已初始化。稍后您仍然可以在代码中执行此操作,但要证明的错误会有点混乱。
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='System.Windows.Window', AncestorLevel='1''.
BindingExpression:Path=DataContext.Txt; DataItem=null; target element
is 'Button' (Name=''); target property is 'Content' (type '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>
Template.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);
}
与其使用许多依赖属性,不如使用样式方法。样式包含按钮控件可用的每个 属性。
我会为 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>
主要问题是Template components are initialized before mainwindow components.I意味着mainwindow中按钮的所有属性都是在模板class中的按钮初始化之后设置的。因此,正如您所说,值设置为空。我想说的是关于初始化的顺序 objects.If 你用这样的方法来做一个技巧 ;
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)));
然后在 Main Window:
<Window.Resources>
<Button x:Key="UserButton"
Background="Yellow"
Content="Actual Button"
/>
</Window.Resources>
<Grid>
<templateCode:Template myButton="{StaticResource UserButton}"/>
</Grid>
这样做的好处是 Visual Studio 足够智能,可以在设计时显示此代码,并且整体代码更少。
您可以在内容控件或默认样式中为您的按钮设置一些不变的东西(如位置、字体和颜色),然后只修改您按钮需要的部分。
在工作中,我有几个页面,每个页面的按钮都在相同的位置,并且具有相同的属性。每个页面也有细微差别。为此,我们创建了一个 userControl 模板并将所有按钮放入其中,然后将该用户控件应用于所有页面。但是,现在很难从每个页面的 xaml 访问按钮和修改它们,因为它们位于页面上的 UserControl 中..... 我如何从每个页面优雅地访问按钮页数?
我试过的:
目前,我们绑定了一堆依赖属性。我不喜欢这个选项,因为我有很多按钮,并且需要控制这些按钮的很多属性。结果是成百上千的依赖属性,当我们需要改变某些东西时,真的是一团糟。
另一种方法是使用样式。我一般喜欢这种方法,但是因为这些按钮在另一个控件中,所以很难修改它们,而且模板一次只能完全适合一个按钮。
Adam Kemp 发布了关于让用户只需插入他们自己的按钮 here,这是我目前正在尝试实现/修改的方法。不幸的是,我无法访问 Xamarin。
虽然在代码运行时插入了模板 ,但模板 并未 正确更新按钮。如果我在 MyButton Setter 中放置一个断点,我可以看到 value 实际上是一个空按钮,而不是我在 main window 中分配的按钮。我该如何解决这个问题?
这是一些简化的代码:
我的模板 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>
我的模板用户控件的隐藏代码:
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();
}
}
}
编辑: 虽然我想删除 template userControl 中不必要的依赖属性,但我仍然想在按钮的属性来自XAML。
如果您可以将对按钮的更改分组到数据上下文中的一个或多个属性,则可以使用 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 使用多个条件。
一种选择是使用 < ![CDATA[ *** ]]>
在 xaml 页面上简单地开始编写 C#在 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>
然后在 Initialize Object() 之后立即调用 OnStartup()。
虽然这确实允许您编辑 xaml 中的特定属性,但这与仅在代码后面编写代码大致相同,其他人期望它是这样。
您可以在 UserControl
上注册一个依赖项 属性 Button
并在其 PropertyChangedCallback
中处理初始化。
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));
}
}
}
}
}
Template.xaml
UserControl DataContext
继承自parent,无需显式设置
<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>
编辑 - 绑定 Button.Content
3 种方法:
1.依赖属性
迄今为止最好的方法。为 Button
上的每个 属性 创建 UserControl
DP 当然是矫枉过正,但对于那些你想绑定到 ViewModel / MainWindow DataContext 的人来说这是有意义的。
加入Template.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); }
}
Template.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 道具(例如 Txt
)无法在 Resources
中引用,因为所有内容都已初始化。稍后您仍然可以在代码中执行此操作,但要证明的错误会有点混乱。
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Txt; DataItem=null; target element is 'Button' (Name=''); target property is 'Content' (type '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>
Template.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);
}
与其使用许多依赖属性,不如使用样式方法。样式包含按钮控件可用的每个 属性。
我会为 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>
主要问题是Template components are initialized before mainwindow components.I意味着mainwindow中按钮的所有属性都是在模板class中的按钮初始化之后设置的。因此,正如您所说,值设置为空。我想说的是关于初始化的顺序 objects.If 你用这样的方法来做一个技巧 ;
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)));
然后在 Main Window:
<Window.Resources>
<Button x:Key="UserButton"
Background="Yellow"
Content="Actual Button"
/>
</Window.Resources>
<Grid>
<templateCode:Template myButton="{StaticResource UserButton}"/>
</Grid>
这样做的好处是 Visual Studio 足够智能,可以在设计时显示此代码,并且整体代码更少。
您可以在内容控件或默认样式中为您的按钮设置一些不变的东西(如位置、字体和颜色),然后只修改您按钮需要的部分。