Как получить доступ к кнопкам внутри UserControl из xaml?
на работе у меня есть несколько страниц, каждая с кнопки в тех же местах, и с теми же свойствами. Каждая страница также имеет незначительные отличия. С этой целью мы создали шаблон userControl и поместили в него все кнопки, а затем применили этот пользовательский элемент управления ко всем страницам. Однако теперь довольно сложно получить доступ к кнопкам и изменить их из xaml каждой страницы, потому что они находятся внутри UserControl на странице..... как элегантно получить доступ к кнопкам от каждого Пейдж?
что я пробовал:
В настоящее время мы привязываемся к куче свойств зависимостей. Мне не нравится эта опция, потому что у меня много кнопок, и мне нужно контролировать много свойств на этих кнопках. Результатом являются сотни свойств зависимостей и настоящий беспорядок, чтобы пробраться, когда нам нужно что-то изменить.
другой метод - использовать стили. Мне нравится этот метод в целом, но потому что эти кнопки внутри другого элемента управления становится трудно модифицировать их, а шаблон только для одной кнопки, в одно время.
Адам Кемп опубликовал о том, чтобы позволить пользователю просто вставить свою собственную кнопку здесь, и это метод, который я в настоящее время пытаюсь имплиментировать / модифицировать. К сожалению, у меня нет доступа к 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>
вы можете зарегистрировать свойство зависимостей 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, это примерно то же самое, что просто писать код в коде позади, где другие ожидают его.