c# - que - Cómo crear un UserControl de WPF con contenido NAMED
wpf c# tutorial (9)
Elegí crear una propiedad adicional para cada elemento que necesito obtener:
public FrameworkElement First
{
get
{
if (Controls.Count > 0)
{
return Controls[0];
}
return null;
}
}
Esto me permite acceder a los elementos secundarios en XAML:
<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/>
Tengo un conjunto de controles con comandos adjuntos y lógica que se reutilizan constantemente de la misma manera. Decidí crear un control de usuario que contenga todos los controles comunes y la lógica.
Sin embargo, también necesito el control para poder mantener contenido que se pueda nombrar. Intenté lo siguiente:
<UserControl.ContentTemplate>
<DataTemplate>
<Button>a reused button</Button>
<ContentPresenter Content="{TemplateBinding Content}"/>
<Button>a reused button</Button>
</DataTemplate>
</UserControl.ContentTemplate>
Sin embargo, parece que cualquier contenido colocado dentro del control de usuario no puede nombrarse. Por ejemplo, si uso el control de la siguiente manera:
<lib:UserControl1>
<Button Name="buttonName">content</Button>
</lib:UserControl1>
Recibo el siguiente error:
No se puede establecer el valor del atributo Name ''buttonName'' en el elemento ''Button''. ''Botón'' está bajo el alcance del elemento ''UserControl1'', que ya tenía un nombre registrado cuando se definió en otro ámbito.
Si elimino el buttonName, entonces compila, sin embargo necesito poder nombrar el contenido. ¿Cómo puedo conseguir esto?
En ocasiones, es posible que solo necesites hacer referencia al elemento desde C #. Dependiendo del caso de uso, puede establecer un x:Uid
lugar de x:Name
y acceder a los elementos llamando a un método de buscador Uid como Get object por su Uid en WPF .
La respuesta es no usar UserControl para hacerlo.
Crea una clase que extienda ContentControl
public class MyFunkyControl : ContentControl
{
public static readonly DependencyProperty HeadingProperty =
DependencyProperty.Register("Heading", typeof(string),
typeof(HeadingContainer), new PropertyMetadata(HeadingChanged));
private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((HeadingContainer) d).Heading = e.NewValue as string;
}
public string Heading { get; set; }
}
luego usa un estilo para especificar los contenidos
<Style TargetType="control:MyFunkyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="control:MyFunkyContainer">
<Grid>
<ContentControl Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
y finalmente, úsalo
<control:MyFunkyControl Heading="Some heading!">
<Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>
Otra alternativa que he usado es simplemente establecer la propiedad Name
en el evento Loaded
.
En mi caso, tenía un control bastante complejo que no quería crear en el código subyacente, y buscaba un control opcional con un nombre específico para cierto comportamiento, y dado que noté que podía establecer el nombre en un DataTemplate
Pensé que podría hacerlo también en el evento Loaded
.
private void Button_Loaded(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
b.Name = "buttonName";
}
Otra solución más: hacer referencia al elemento como RelativeSource .
Parece que esto no es posible cuando se usa XAML. Los controles personalizados parecen ser excesivos cuando en realidad tengo todos los controles que necesito, pero solo necesito agruparlos con un poco de lógica y permitir contenido con nombre.
La solución en solution como sugiere mackenir, parece tener el mejor compromiso. Una forma de extender la solución de JD para permitir que los controles aún se definan en XAML podría ser la siguiente:
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var grid = new Grid();
var content = new ContentPresenter
{
Content = Content
};
var userControl = new UserControlDefinedInXAML();
userControl.aStackPanel.Children.Add(content);
grid.Children.Add(userControl);
Content = grid;
}
En mi ejemplo anterior he creado un control de usuario llamado UserControlDefinedInXAML que se define como cualquier control de usuario normal usando XAML. En mi UserControlDefinedInXAML tengo un StackPanel llamado aStackPanel dentro del cual quiero que aparezca mi contenido nombrado.
Puede usar esta ayuda para el nombre del conjunto dentro del control del usuario:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
namespace UI.Helpers
{
public class UserControlNameHelper
{
public static string GetName(DependencyObject d)
{
return (string)d.GetValue(UserControlNameHelper.NameProperty);
}
public static void SetName(DependencyObject d, string val)
{
d.SetValue(UserControlNameHelper.NameProperty, val);
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.RegisterAttached("Name",
typeof(string),
typeof(UserControlNameHelper),
new FrameworkPropertyMetadata("",
FrameworkPropertyMetadataOptions.None,
(d, e) =>
{
if (!string.IsNullOrEmpty((string)e.NewValue))
{
string[] names = e.NewValue.ToString().Split(new char[] { '','' });
if (d is FrameworkElement)
{
((FrameworkElement)d).Name = names[0];
Type t = Type.GetType(names[1]);
if (t == null)
return;
var parent = FindVisualParent(d, t);
if (parent == null)
return;
var p = parent.GetType().GetProperty(names[0], BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
p.SetValue(parent, d, null);
}
}
}));
public static DependencyObject FindVisualParent(DependencyObject child, Type t)
{
// get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
// we’ve reached the end of the tree
if (parentObject == null)
{
var p = ((FrameworkElement)child).Parent;
if (p == null)
return null;
parentObject = p;
}
// check if the parent matches the type we’re looking for
DependencyObject parent = parentObject.GetType() == t ? parentObject : null;
if (parent != null)
{
return parent;
}
else
{
// use recursion to proceed with next level
return FindVisualParent(parentObject, t);
}
}
}
}
y su ventana o código de control detrás de usted controla por propiedad:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public Button BtnOK { get; set; }
}
su ventana xaml:
<Window x:Class="user_Control_Name.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:user_Control_Name"
xmlns:helper="clr-namespace:UI.Helpers" x:Name="mainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<test:TestUserControl>
<Button helper:UserControlNameHelper.Name="BtnOK,user_Control_Name.MainWindow"/>
</test:TestUserControl>
<TextBlock Text="{Binding ElementName=mainWindow,Path=BtnOK.Name}"/>
</Grid>
</Window>
UserControlNameHelper obtenga su nombre de control y su nombre de Clase para establecer Control en Propiedad.
Tuve el mismo problema al usar un TabControl al colocar un grupo de controles nombrados.
Mi solución fue utilizar una plantilla de control que contiene todos mis controles para mostrarse en una página de pestañas. Dentro de la plantilla puede usar la propiedad Nombre y también los datos se unen a las propiedades del control nombrado de otros controles al menos dentro de la misma plantilla.
Como contenido del control TabItem, use un control simple y configure ControlTemplate en consecuencia:
<Control Template="{StaticResource MyControlTemplate}"/>
Acceder a esos controles nombrados dentro de la plantilla desde el código detrás de usted necesitaría usar el árbol visual.
<Popup>
<TextBox Loaded="BlahTextBox_Loaded" />
</Popup>
Código detrás:
public TextBox BlahTextBox { get; set; }
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e)
{
BlahTextBox = sender as TextBox;
}
La solución real sería que Microsoft solucionara este problema, así como todos los demás con árboles visuales rotos, etc. Hipotéticamente hablando.