2009-04-15 21 views
73

Ho un set di controlli con comandi e logica allegati che vengono costantemente riutilizzati nello stesso modo. Ho deciso di creare un controllo utente che contiene tutti i controlli e la logica comuni.Come creare un UserControl WPF con contenuto NAMED

Tuttavia, ho anche bisogno che il controllo sia in grado di contenere il contenuto che può essere denominato. Ho provato il seguente:

<UserControl.ContentTemplate> 
    <DataTemplate> 
     <Button>a reused button</Button> 
     <ContentPresenter Content="{TemplateBinding Content}"/> 
     <Button>a reused button</Button> 
    </DataTemplate> 
</UserControl.ContentTemplate> 

Tuttavia sembra che qualsiasi contenuto inserito nel controllo utente non possa essere denominato. Per esempio, se io uso il controllo nel seguente modo:

<lib:UserControl1> 
    <Button Name="buttonName">content</Button> 
</lib:UserControl1> 

ricevo il seguente errore:

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.

Se rimuovo il buttonname, poi si compila, però ho bisogno di essere in grado di nome il contenuto. Come posso raggiungere questo obiettivo?

+0

Questa è una coincidenza. Stavo per fare questa domanda! Ho lo stesso problema. Elaborazione di pattern UI comuni in un UserControl, ma che desiderano fare riferimento all'interfaccia utente del contenuto per nome. – mackenir

+1

Perché non usi il metodo ResourceDictionary? Definire il DataTemplate in esso. O utilizzare la parola chiave BasedOn per ereditare il controllo. Solo alcuni percorsi che avrei seguito prima di eseguire l'UI code-behind in WPF ... –

+2

Questo ragazzo ha trovato una [soluzione] (http://blog.bluecog.co.nz/archives/2007/08/27/wpf-cannot -set-name-attribute /) che comporta la rimozione del file XAML del suo controllo personalizzato e la creazione dell'interfaccia utente del controllo personalizzato a livello di programmazione. Questo [post di blog] (http://rrelyea.spaces.live.com/Blog/cns!167AD7A5AB58D5FE!2130.entry?wa=wsignin1.0&sa=752255111) ha altro da dire sull'argomento. – mackenir

risposta

16

Sembra che questo non sia possibile quando XAML viene utilizzato. I controlli personalizzati sembrano essere eccessivi quando in realtà ho tutti i controlli di cui ho bisogno, ma ho solo bisogno di raggrupparli insieme con un po 'di logica e consentire il contenuto con nome.

La soluzione su JD's blog come suggerisce mackenir, sembra avere il miglior compromesso. Un modo per estendere la soluzione di JD Affinché i controlli ancora essere definiti in XAML potrebbe essere il seguente:

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;   
    } 

Nell'esempio sopra ho creato un controllo utente chiamato UserControlDefinedInXAML che definiscono come qualsiasi normali controlli utente utilizzando XAML. Nel mio UserControlDefinedInXAML ho uno StackPanel chiamato aStackPanel all'interno del quale voglio che appaia il mio contenuto con nome.

+0

Ho scoperto che ricevo problemi con l'associazione dei dati quando utilizzo questo meccanismo di re-parenting del contenuto.Il data-binding sembra impostato correttamente, ma il popolamento iniziale dei controlli dall'origine dati non funziona correttamente. Penso che il problema sia limitato ai controlli che non sono il figlio diretto del presentatore di contenuti. – mackenir

+0

Da allora non ho avuto la possibilità di sperimentare questo, ma non ho avuto alcun problema di associazione dati ai controlli definiti in UserControlDefinedInXAML (dall'esempio precedente) o ai controlli aggiunti a ContentPresenter finora. Sono stato collegato al codice solo attraverso il codice (non XAML, non sono sicuro che ciò faccia la differenza). – Ryan

+0

Sembra che faccia la differenza. Ho appena provato a utilizzare XAML per i miei binding di dati nel caso che hai descritto e non funziona. Ma se lo metto in codice, funziona! – Ryan

31

La risposta è di non utilizzare un UserControl per farlo.

Creare una classe che estende 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; } 
} 

quindi utilizzare uno stile per specificare il contenuto

<Style TargetType="control:MyFunkyControl"> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="control:MyFunkyContainer"> 
       <Grid> 
        <ContentControl Content="{TemplateBinding Content}"/> 
       </Grid> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

e infine - lo usano

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

Ho trovato questo in realtà la soluzione più comoda in quanto è possibile ritagliare il ControlTemplate in un normale UserControl utilizzando il designer e trasformarlo in uno stile con il modello di controllo associato. –

+1

Hmm, è un po 'strano che funzioni per te, perché ho provato ad applicare questo approccio e nel mio caso ho ancora questo errore infame. – greenoldman

+1

Lo stesso per me: nessun effetto. :( – Badiboy

3

Un'altra alternativa I' è usato solo per impostare il Name proprietà nell'evento Loaded.

Nel mio caso, ho avuto un controllo piuttosto complesso che non volevo creare nel code-behind, e ho cercato un controllo opzionale con un nome specifico per un determinato comportamento, e poiché ho notato che potevo impostare il nome in un DataTemplate Ho pensato che avrei potuto farlo anche nell'evento Loaded.

private void Button_Loaded(object sender, RoutedEventArgs e) 
{ 
    Button b = sender as Button; 
    b.Name = "buttonName"; 
} 
+2

Se si esegue questa operazione, i collegamenti che utilizzano il nome non funzioneranno ... a meno che non si imposti i collegamenti nel codice. – Tower

0

Ho scelto di creare una proprietà in più per ogni elemento ho bisogno di ottenere:

public FrameworkElement First 
    { 
     get 
     { 
      if (Controls.Count > 0) 
      { 
       return Controls[0]; 
      } 
      return null; 
     } 
    } 

Questo mi permette di accedere agli elementi figlio in XAML:

<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/> 
3

volte potrebbe essere sufficiente fare riferimento all'elemento da C#. A seconda del caso d'uso, è possibile impostare uno x:Uid anziché uno x:Name e accedere agli elementi chiamando un metodo di ricerca UID come Get object by its Uid in WPF.

0

È possibile utilizzare questo helper nome gruppo all'interno del controllo utente:

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); 
      } 
     } 
    } 
} 

e la vostra finestra o controllo del codice Dietro di set che si controllano da Property:

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

    } 

    public Button BtnOK { get; set; } 
} 

finestra 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 ottenere il nome del controllo e il nome della classe per impostare Controllo su P ROPRIETÀ.

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

codice dietro:

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

La vera soluzione sarebbe per Microsoft per risolvere questo problema, così come tutti gli altri con alberi visivi rotti ecc ipoteticamente parlando.

0

Ancora un'altra soluzione: fare riferimento all'elemento come RelativeSource.

0

Ho avuto lo stesso problema utilizzando un TabControl quando inserisco un gruppo di controlli con nome in.

La mia soluzione era quella di utilizzare un modello di controllo che contenga tutti i miei controlli per essere mostrato in una pagina di tabulazione. All'interno del modello è possibile utilizzare la proprietà Name e anche il binding dei dati alle proprietà del controllo denominato da altri controlli, almeno all'interno dello stesso modello.

Come contenuto del controllo TabItem, utilizzare un semplice controllo e impostare la ControlTemplate di conseguenza:

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

Accesso quelli nominati il ​​controllo all'interno del template dal codice dietro si avrebbe bisogno di utilizzare la struttura ad albero visuale.

Problemi correlati