2009-04-15 27 views
73

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.Cómo crear un WPF UserControl con contenido NOMBRADO

Sin embargo, también necesito el control para poder contener contenido que se puede 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:

Cannot set Name attribute value 'buttonName' on element 'Button'. 'Button' is under the scope of element 'UserControl1', which already had a name registered when it was defined in another scope.

Si quito el buttonname, a continuación, se compila, sin embargo tengo que ser capaz de nombrar el contenido. ¿Cómo puedo conseguir esto?

+0

Esto es una coincidencia. ¡Estaba a punto de hacer esta pregunta! Tengo el mismo problema. Factorizar el patrón de IU común en un UserControl, pero querer hacer referencia a la IU de contenido por nombre. – mackenir

+1

¿Por qué no usa ResourceDictionary? Defina el DataTemplate en él. O use la palabra clave BasedOn para heredar el control. Solo algunos caminos que seguiría antes de hacer una IU de código subyacente en WPF ... –

+2

Este tipo encontró una [solución] (http://blog.bluecog.co.nz/archives/2007/08/27/wpf-cannot -set-name-attribute /) que implica deshacerse del archivo XAML de su control personalizado y crear la interfaz de usuario del control personalizado mediante programación. Esta [publicación de blog] (http://rrelyea.spaces.live.com/Blog/cns!167AD7A5AB58D5FE!2130.entry?wa=wsignin1.0&sa=752255111) tiene más que decir sobre el tema. – mackenir

Respuesta

16

Parece que esto no es posible cuando se utiliza 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 JD's blog como sugiere mackenir, parece tener el mejor compromiso. A manera de extender la solución de JD para permitir controles para todavía ser definidos en XAML podría ser como sigue:

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 es definir como cualquier controles de usuario normales usando XAML. En mi UserControlDefinedInXAML tengo un StackPanel llamado aStackPanel dentro del cual quiero que aparezca mi contenido nombrado.

+0

He estado descubriendo que tengo problemas de enlace de datos cuando uso este mecanismo para volver a criar el contenido.La vinculación de datos parece configurada correctamente, pero el llenado inicial de los controles de la fuente de datos no funciona correctamente. Creo que el problema se limita a los controles que no son hijos directos del presentador de contenido. – mackenir

+0

No tuve la oportunidad de experimentar con esto desde entonces, pero no he tenido problemas para enlazar datos con los controles definidos en UserControlDefinedInXAML (del ejemplo anterior) o los controles agregados a ContentPresenter hasta el momento. Sin embargo, he estado enlazando datos a través del código (no XAML, no estoy seguro si eso hace la diferencia). – Ryan

+0

Parece que HACE la diferencia. Intenté usar XAML para mis enlaces de datos en el caso que describió y no funciona. Pero si lo configuro en código, ¡funciona! – Ryan

31

La respuesta es no utilizar un UserControl para hacerlo.

Crear una clase que se extiende 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; } 
} 

a continuación, utilizar un estilo para especificar el contenido

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

<control:MyFunkyControl Heading="Some heading!">    
    <Label Name="WithAName">Some cool content</Label> 
</control:MyFunkyControl> 
+2

Encontré esto realmente la solución más cómoda ya que puede desarrollar ControlTemplate en un UserControl ordinario que usa el diseñador y que lo transforma en un estilo con una plantilla de control asociada. –

+1

Hmm, es un poco extraño que funcione para usted, porque traté de aplicar este enfoque y en mi caso todavía recibo este infame error. – greenoldman

+1

Lo mismo para mí: sin efecto. :( – Badiboy

3

Otra alternativa I' ve utilizado es simplemente configurar el Name propiedad 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 como 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"; 
} 
+2

Si haces esto, las vinculaciones que usan el nombre no funcionarán ... a menos que establezcas los enlaces en el código. – Tower

0

que he elegido para crear una propiedad adicional para cada elemento que necesito para 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}"/> 
3

veces es posible que solo necesite hacer referencia al elemento desde C#. Dependiendo del caso de uso, puede establecer un x:Uid en lugar de un x:Name y acceder a los elementos llamando a un método de buscador Uid como Get object by its Uid in WPF.

0

Puede utilizar esta ayuda para el nombre de conjunto dentro del control de 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 conjunto que controlan por la propiedad:

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     InitializeComponent(); 

    } 

    public Button BtnOK { get; set; } 
} 

la 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 P Roperty.

0
<Popup> 
    <TextBox Loaded="BlahTextBox_Loaded" /> 
</Popup> 

Código atrás:

public TextBox BlahTextBox { get; set; } 
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e) 
{ 
    BlahTextBox = sender as TextBox; 
} 

La solución real sería para Microsoft para solucionar este problema, así como todos los demás con los árboles rotos visuales etc. hipotéticamente hablando.

0

Otra solución alternativa: referencia el elemento como RelativeSource.

0

Tuve el mismo problema al usar un TabControl al colocar un grupo de controles nombrados en.

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, utilice un control sencillo y establecer el ControlTemplate en consecuencia:

<Control Template="{StaticResource MyControlTemplate}"/> 

Acceso a los nombrados de control dentro de la plantilla de código detrás de lo que tendría que utilizar el árbol visual.

Cuestiones relacionadas