J'écris un contrôle WPF qui est censé être un conteneur de la même manière que Border et ScrollViewer sont des conteneurs. Il s'appelle EllipsisButtonControl, et il est censé placer un bouton de sélection à droite de son contenu. Voici un exemple de la façon dont j'ai l'intention de l'utiliser:

<local:EllipsisButtonControl>
    <TextBlock Text="Testing" />
</local:EllipsisButtonControl>

Voici le XAML pour EllipsisButtonControl:

<ContentControl
    x:Class="WpfApplication1.EllipsisButtonControl"
    x:Name="ContentControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    d:DesignHeight="30" d:DesignWidth="300">

    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" />

        <Button Grid.Column="1" Command="{Binding  ElementName=ContentControl, Path=Command}" Margin="3,0" Width="30" Height="24" MaxHeight="24" VerticalAlignment="Stretch" Content="..." />

    </Grid>

</ContentControl>

Et voici le code derrière:

using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class EllipsisButtonControl
    {
        public EllipsisButtonControl()
        {
            InitializeComponent();
        }

        public static string GetCommand(DependencyObject obj)
        {
            return (string)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, string value)
        {
            obj.SetValue(CommandProperty, value);
        }

        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
                name: "Command",
                propertyType: typeof(ICommand),
                ownerType: typeof(EllipsisButtonControl),
                defaultMetadata: new UIPropertyMetadata());
    }
}

Cela ne marche pas. Il plante le Designer avec un System.Runtime.Remoting.RemotingException.

Je pense que la liaison sur le ContentPresenter du EllipsisButtonControl XAML est fausse, mais je ne sais pas comment y remédier. Quelle est la syntaxe appropriée pour que cette ligne fasse référence au contenu du contrôle? (par exemple, le TextBlock défini dans l'exemple d'utilisation)

Modifier:

Poke a fourni une réponse complète ci-dessous (y compris le code de travail), mais pour le bénéfice d'autres personnes qui pourraient partager mon malentendu initial, permettez-moi de résumer le concept clé ici: Un contrôle de conteneur ne peut pas "placer du contenu" en soi. Il obtient l'effet souhaité en définissant un modèle qui modifie la façon dont le XAML appelant présente le contenu. Le reste de la solution découle de cette prémisse.

3
Lork 26 avril 2017 à 04:39

3 réponses

Meilleure réponse
<local:EllipsisButtonControl>
    <TextBlock Text="Testing" />
</local:EllipsisButtonControl>

Cela définit le Content de votre contrôle utilisateur. Mais il en va de même pour ce qui suit dans le XAML du contrôle utilisateur:

<ContentControl …>
    <Grid>
        …
    </Grid>
</ContentControl>

Le XAML appelant a la priorité ici, donc tout ce que vous faites à l'intérieur du XAML de ce contrôle utilisateur est en fait ignoré.

La solution ici est de définir le template du champ utilisateur. Le modèle, dans ce cas le modèle de contrôle du contrôle, détermine comment le contrôle lui-même est rendu. Le modèle le plus simple pour un contrôle utilisateur (et aussi sa valeur par défaut) utilise simplement un ContentPresenter là-bas, mais bien sûr, vous voulez ajouter des éléments autour de cela, nous devons donc écraser le modèle. Cela ressemble généralement à ceci:

<ContentControl …>
    <!-- We are setting the `Template` property -->
    <ContentControl.Template>
        <!-- The template value is of type `ControlTemplate` and we should
             also set the target type properly so binding paths can be resolved -->
        <ControlTemplate>

            <!-- This is where your control code actually goes -->

        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>

Maintenant, c'est le cadre dont vous avez besoin pour que cela fonctionne. Cependant, une fois que vous êtes dans le modèle de contrôle, vous devez utiliser le type de liaison approprié. Étant donné que nous écrivons un modèle et que nous voulons nous lier aux propriétés du contrôle parent, nous devons spécifier le contrôle parent comme source relative dans les liaisons. Mais le moyen le plus simple de le faire est d'utiliser simplement le TemplateBinding extension de balisage. En utilisant cela, un ContentPresenter peut être placé comme ceci à l'intérieur du ControlTemplate ci-dessus:

<ContentPresenter Content="{TemplateBinding Content}" />

Et cela devrait être tout ce dont vous avez besoin ici pour que le présentateur de contenu fonctionne.

Cependant, maintenant que vous utilisez un modèle de contrôle, vous devez bien sûr également ajuster vos autres liaisons. En particulier la liaison à votre propriété de dépendance personnalisée Command. Cela ressemblerait généralement à la liaison de modèle à Content mais comme notre modèle de contrôle cible le type ContentControl et qu'un ContentControl n'a pas votre propriété personnalisée, nous devons faire référence explicitement votre propriété de dépendance personnalisée ici:

<Button Command="{TemplateBinding local:EllipsisButtonControl.Command}" … />

Une fois que nous avons cela, toutes les liaisons devraient fonctionner correctement. (Au cas où vous vous poseriez la question maintenant: Oui, la liaison cible toujours la propriété de dépendance statique sur le type)


Donc, pour résumer tout cela, votre contrôle de contenu personnalisé devrait ressembler à ceci:

<ContentControl
        x:Class="WpfApplication1.EllipsisButtonControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApplication1"
        d:DesignHeight="30" d:DesignWidth="300" mc:Ignorable="d">
    <ContentControl.Template>
        <ControlTemplate TargetType="ContentControl">

            <Grid>
                <ContentPresenter Grid.Column="0"
                        Content="{TemplateBinding Content}" />

                <Button Grid.Column="1" Content="…"
                        Command="{TemplateBinding local:EllipsisButtonControl.Command}" />
            </Grid>

        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>
1
poke 26 avril 2017 à 08:20

Essayez de remplacer cette ligne:

<ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" />

Avec ça

<ContentPresenter Grid.Column="0" Content={Binding Content} />

Dans votre code existant, vous faites en sorte que ContentPresenter affiche le contenu généré de EllipsesButtonControl, qui inclut le ContentPresenter qui doit rendre le contenu généré de ElipsesButtonControl qui inclut le {{X4 }} ..... Récursivité illimitée.

1
Andrew Shepherd 26 avril 2017 à 01:57

Le XAML de votre EllipsisButtonControl définit déjà son contenu sur la grille de niveau supérieur. Ce que vous vouliez probablement, c'est créer un ControlTemplate, par exemple comme ça:

<ContentControl x:Class="WpfApplication1.EllipsisButtonControl"
                x:Name="ContentControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                mc:Ignorable="d" d:DesignHeight="30" d:DesignWidth="300">
    <ContentControl.Template>
        <ControlTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <ContentPresenter Grid.Column="0"
                    Content="{Binding ElementName=ContentControl, Path=Content}"/>

                <Button Grid.Column="1"
                    Command="{Binding ElementName=ContentControl, Path=Command}"
                    Margin="3,0" Width="30" Height="24" MaxHeight="24"
                    VerticalAlignment="Stretch" Content="..." />
            </Grid>
        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>
1
Clemens 26 avril 2017 à 07:35