Как получить доступ к кнопкам внутри UserControl из xaml?
На работе у меня есть несколько страниц, каждая с кнопками в тех же местах и с теми же свойствами. Каждая страница также имеет незначительные отличия. С этой целью мы создали шаблон UserControl и поместили в него все кнопки, а затем применили этот пользовательский элемент управления ко всем страницам. Однако теперь довольно сложно получить доступ к кнопкам и изменить их с каждой страницы xaml, потому что они находятся внутри UserControl на странице..... Как мне элегантно получить доступ к кнопкам с каждой страницы?
Что я пробовал:
-
В настоящее время мы связываемся с связью свойств зависимостей. Мне не нравится этот вариант, потому что у меня много кнопок, и вам нужно управлять множеством свойств на этих кнопках. Результатом являются сотни свойств зависимостей и реальный беспорядок, который нужно пропустить, когда нам нужно что-то изменить.
-
Другой способ - использовать стили. Мне нравится этот метод в целом, но поскольку эти кнопки находятся внутри другого элемента управления, становится сложно их модифицировать, и шаблон будет точно соответствовать только одной кнопке за один раз.
-
Adam Kemp опубликовал о том, чтобы позволить пользователю просто вставить свою собственную кнопку здесь, и это метод я в настоящее время пытается внедрить/изменить. К сожалению, у меня нет доступа к Xamarin.
Хотя шаблон вставляется при запуске кода, шаблон не обновляет кнопку правильно. Если я установил точку останова в MyButton Setter, я вижу, что значение на самом деле является пустой, а не той, которую я назначил в своем главном окне. Как это исправить?
Здесь приведен упрощенный код:
Мой шаблон 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>
Мой шаблон UserControl 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
}
}
}
}
И теперь приложение, в котором я хочу его использовать:
<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();
}
}
}
Изменить: Пока я хочу удалить ненужные свойства зависимостей в шаблоне userControl, мне все равно хотелось бы установить привязки свойств кнопки из XAML.
Ответы
Ответ 1
Другой вариант, основанный на ответе @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 достаточно умен, чтобы показывать этот код во время разработки, а также иметь меньше кода в целом.
Вы можете установить для своей кнопки все элементы (например, местоположение, шрифт и раскраску), либо в элементе управления контентом, либо в стиле по умолчанию, а затем изменить только те части, которые вам нужны для кнопки.
Ответ 2
Вы можете зарегистрировать Свойство зависимостей Button
на UserControl
и обработать инициализацию в 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
наследуется от родителя, не нужно явно указывать его
<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, это имеет смысл.
Добавление в 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: 4: не удается найти источник для привязки с ссылка 'RelativeSource FindAncestor, AncestorType = 'System.Windows.Window', AncestorLevel = '1' '. BindingExpression: Path = DataContext.Txt; DataItem = NULL; целевой элемент 'Button' (Name= ''); target - это "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>
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);
}
Ответ 3
а не использовать многие свойства зависимостей, предпочитайте стиль. Стиль содержит все свойства, доступные для элемента управления 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>
![введите описание изображения здесь]()
Ответ 4
Если вы можете сгруппировать свои изменения на своих кнопках с одним или несколькими свойствами в вашем 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.
Ответ 5
Основная проблема заключается в том, что компоненты шаблона инициализируются перед компонентами mainwindow. Я имею в виду, что все свойства кнопки в mainwindow устанавливаются после инициализации кнопки в классе шаблона. Поэтому, как вы сказали, значение устанавливает значение 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;
}
}
Это будет работать бесспорно.
Ответ 6
Один из вариантов - просто начать писать С# на странице xaml, используя <! [CDATA [***]] >
В главном окне .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>
Затем сразу после инициализации объекта() вы вызываете OnStartup().
Хотя это позволяет вам редактировать определенные свойства в xaml, это примерно то же самое, что просто писать код в коде позади, где другие ожидают его.
Ответ 7
Использовать лямбда-выражение или делегировать методы для установки определенного метода для каждой кнопки с каждой страницей