web-dev-qa-db-fra.com

Comment créer un WPF UserControl avec du contenu NAMED

J'ai un ensemble de contrôles avec des commandes et une logique attachées qui sont constamment réutilisés de la même manière. J'ai décidé de créer un contrôle utilisateur contenant tous les contrôles et la logique courants.

Cependant, j'ai également besoin du contrôle pour pouvoir contenir du contenu pouvant être nommé. J'ai essayé ce qui suit:

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

Cependant, il semble qu'aucun contenu placé à l'intérieur du contrôle utilisateur ne puisse être nommé. Par exemple, si j'utilise le contrôle de la manière suivante:

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

Je reçois l'erreur suivante:

Impossible de définir la valeur d'attribut Nom 'buttonName' sur l'élément 'Button'. 'Button' est sous la portée de l'élément 'UserControl1', qui avait déjà un nom enregistré quand il a été défini dans une autre portée.

Si je supprime le ButtonName, il se compile, mais je dois pouvoir nommer le contenu. Comment puis-je atteindre cet objectif?

87
Ryan

Il semble que ce ne soit pas possible lorsque XAML est utilisé. Les contrôles personnalisés semblent être une exagération quand j'ai réellement tous les contrôles dont j'ai besoin, mais il suffit de les regrouper avec un peu de logique et d'autoriser le contenu nommé.

La solution sur le blog de JD comme le suggère mackenir, semble avoir le meilleur compromis. Un moyen d'étendre la solution de JD pour permettre aux contrôles d'être encore définis dans XAML pourrait être le suivant:

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

Dans mon exemple ci-dessus, j'ai créé un contrôle utilisateur appelé UserControlDefinedInXAML qui est défini comme n'importe quel contrôle utilisateur normal utilisant XAML. Dans mon UserControlDefinedInXAML, j'ai un StackPanel appelé aStackPanel dans lequel je veux que mon contenu nommé apparaisse.

17
Ryan

La réponse est de ne pas utiliser un UserControl pour le faire.

Créez une classe qui étend 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; }
}

puis utilisez un style pour spécifier le contenu

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

et enfin - l'utiliser

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

Parfois, vous devrez peut-être simplement référencer l'élément à partir de C #. Selon le cas d'utilisation, vous pouvez ensuite définir un x:Uid au lieu d'un x:Name et accéder aux éléments en appelant une méthode Uid Finder comme Récupérer l'objet par son Uid dans WPF .

3
Dani

Une autre alternative que j'ai utilisée consiste à simplement définir la propriété Name dans l'événement Loaded.

Dans mon cas, j'avais un contrôle assez complexe que je ne voulais pas créer dans le code-behind, et il cherchait un contrôle facultatif avec un nom spécifique pour un certain comportement, et puisque j'ai remarqué que je pouvais définir le nom dans un DataTemplate J'ai pensé que je pouvais aussi le faire dans l'événement Loaded.

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

Vous pouvez utiliser cet assistant pour définir le nom dans le contrôle utilisateur:

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

et votre code de fenêtre ou de contrôle derrière vous contrôlez par propriété:

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

    }

    public Button BtnOK { get; set; }
}

votre fenêtre 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 obtient votre nom de contrôle et votre nom de classe pour définir Control sur Property.

1
Ali Yousefi
<Popup>
    <TextBox Loaded="BlahTextBox_Loaded" />
</Popup>

Code derrière:

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

La vraie solution serait que Microsoft corrige ce problème, ainsi que tous les autres avec des arbres visuels cassés, etc. Hypothétiquement parlant.

0
Ed Plunkett

Encore une autre solution: référencez l'élément comme RelativeSource.

0
voccoeisuoi

J'ai choisi de créer une propriété supplémentaire pour chaque élément dont j'ai besoin:

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

Cela me permet d'accéder aux éléments enfants en XAML:

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

J'ai eu le même problème en utilisant un TabControl lors du placement d'un tas de contrôles nommés dans.

Ma solution de contournement consistait à utiliser un modèle de contrôle qui contient tous mes contrôles à afficher dans une page à onglet. À l'intérieur du modèle, vous pouvez utiliser la propriété Name et également la liaison de données aux propriétés du contrôle nommé à partir d'autres contrôles au moins à l'intérieur du même modèle.

En tant que contenu du contrôle TabItem, utilisez un contrôle simple et définissez le ControlTemplate en conséquence:

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

Pour accéder à ces contrôles nommés à l'intérieur du modèle à partir du code derrière, vous devez utiliser l'arborescence visuelle.

0
David Wellna