it-swarm.com.de

So erstellen Sie ein WPF UserControl mit NAMED-Inhalten

Ich habe eine Reihe von Steuerelementen mit angehängten Befehlen und Logik, die ständig auf die gleiche Weise wiederverwendet werden. Ich habe beschlossen, ein Benutzersteuerelement zu erstellen, das alle gemeinsamen Steuerelemente und die Logik enthält.

Ich benötige jedoch auch das Steuerelement, um Inhalte speichern zu können, die benannt werden können. Ich habe folgendes versucht:

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

Es scheint jedoch, dass Inhalte, die sich innerhalb des Benutzersteuerelements befinden, nicht benannt werden können. Zum Beispiel, wenn ich das Steuerelement folgendermaßen verwende:

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

Ich erhalte folgende Fehlermeldung:

Der Name-Attributwert 'buttonName' für das Element 'Button' kann nicht festgelegt werden. 'Button' befindet sich im Bereich des Elements 'UserControl1', für das bereits ein Name registriert war, als es in einem anderen Bereich definiert wurde.

Wenn ich den buttonName entferne, wird er kompiliert, ich muss jedoch in der Lage sein, den Inhalt zu benennen. Wie kann ich das erreichen?

87
Ryan

Es scheint, dass dies nicht möglich ist, wenn XAML verwendet wird. Benutzerdefinierte Steuerelemente scheinen zu viel zu sein, wenn ich tatsächlich über alle erforderlichen Steuerelemente verfüge, sie jedoch nur mit ein wenig Logik gruppieren und benannten Inhalt zulassen muss.

Die Lösung auf JDs Blog , wie Mackenir vorschlägt, scheint den besten Kompromiss zu haben. Eine Möglichkeit, die JD-Lösung so zu erweitern, dass Steuerelemente weiterhin in XAML definiert werden können, ist folgende:

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

In meinem obigen Beispiel habe ich ein Benutzersteuerelement mit dem Namen UserControlDefinedInXAML erstellt, das wie alle normalen Benutzersteuerelemente mit XAML definiert ist. In meinem UserControlDefinedInXAML habe ich ein StackPanel namens aStackPanel, in dem mein benannter Inhalt erscheinen soll.

17
Ryan

Die Antwort ist, kein UserControl dafür zu verwenden.

Erstellen Sie eine Klasse, die ContentControl erweitert

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

verwenden Sie dann einen Stil, um den Inhalt anzugeben

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

und schließlich - benutze es

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

Manchmal müssen Sie möglicherweise nur auf das Element in C # verweisen. Je nach Anwendungsfall können Sie dann ein x:Uid anstelle eines x:Name und greifen Sie auf die Elemente zu, indem Sie eine Uid-Finder-Methode wie Objekt über die Uid in WPF abrufen aufrufen.

3
Dani

Eine andere Alternative, die ich verwendet habe, besteht darin, die Eigenschaft Name im Ereignis Loaded festzulegen.

In meinem Fall hatte ich ein ziemlich komplexes Steuerelement, das ich nicht im Code-Behind erstellen wollte, und es suchte nach einem optionalen Steuerelement mit einem bestimmten Namen für ein bestimmtes Verhalten, und da ich bemerkte, dass ich den Namen in a festlegen konnte DataTemplate Ich dachte, ich könnte es auch im Loaded -Ereignis tun.

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

Sie können diesen Helfer verwenden, um den Namen innerhalb des Benutzersteuerelements festzulegen:

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

und Ihr Fenster- oder Steuercode hinter dem Satz, den Sie durch die Eigenschaft steuern:

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

    }

    public Button BtnOK { get; set; }
}

ihr Fenster 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 ruft Ihren Steuerelementnamen und Ihren Klassennamen ab, um Control auf Property zu setzen.

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

Code dahinter:

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

Die wirkliche Lösung wäre, dass Microsoft dieses Problem behebt, ebenso wie alle anderen mit zerbrochenen visuellen Bäumen usw. Hypothetisch gesehen.

0
Ed Plunkett

Noch eine andere Problemumgehung: Verweisen Sie auf das Element als RelativeSource.

0
voccoeisuoi

Ich habe beschlossen, für jedes Element, das ich erhalten möchte, eine zusätzliche Eigenschaft zu erstellen:

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

Dadurch kann ich auf die untergeordneten Elemente in XAML zugreifen:

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

Ich hatte das gleiche Problem mit einem TabControl beim Platzieren einer Reihe von benannten Steuerelementen in.

Meine Problemumgehung bestand darin, eine Steuerungsvorlage zu verwenden, die alle meine Steuerungen enthält, die auf einer Registerkarte angezeigt werden sollen. Innerhalb der Vorlage können Sie die Name-Eigenschaft verwenden und auch Daten an Eigenschaften des benannten Steuerelements aus anderen Steuerelementen binden, zumindest innerhalb derselben Vorlage.

Verwenden Sie als Inhalt des TabItem-Steuerelements ein einfaches Steuerelement und legen Sie das ControlTemplate entsprechend fest:

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

Wenn Sie über den Code dahinter auf die genannten Steuerelemente in der Vorlage zugreifen, müssen Sie die visuelle Struktur verwenden.

0
David Wellna