44 votes

Exposer les propriétés du contrôle interne pour la liaison dans WPF

[Edit] : J'ai trouvé comment faire ça tout seul. J'ai posté ma solution dans l'espoir qu'elle épargnera à quelqu'un d'autre quelques jours de recherche sur Google. Si vous êtes un gourou de WPF, jetez un coup d'œil à ma solution et faites-moi savoir s'il existe un moyen meilleur / plus élégant / plus efficace de le faire. En particulier, j'aimerais savoir ce que je ne sais pas... comment cette solution va-t-elle me desservir à l'avenir ? Le problème se résume vraiment à l'exposition des propriétés internes du contrôle.

Problème : Je suis en train de créer un code pour générer automatiquement une interface graphique liée à des données dans WPF pour un fichier XML. Je dispose d'un fichier xsd qui peut m'aider à déterminer les types de nœuds, etc. Les éléments simples de type Clé/Valeur sont faciles.

Quand j'analyse cet élément :

<Key>value</Key>

Je peux créer un nouveau "KeyValueControl" et définir la valeur de l'option DataContext à cet élément. Le site KeyValueControl est défini comme un UserControl et a juste quelques liens simples sur elle. Cela fonctionne très bien pour tout XElement simple.

Le XAML à l'intérieur de ce contrôle ressemble à ceci :

<Label Content={Binding Path=Name} /> 
<TextBox Text={Binding Path=Value} />

Le résultat est une ligne qui comporte une étiquette avec le nom de l'élément et une zone de texte avec la valeur que je peux modifier.

Il arrive parfois que je doive afficher des valeurs de référence au lieu de la valeur réelle. Je voudrais créer une 'KeyValueComboBox' similaire à celle ci-dessus KeyValueControl mais être en mesure de spécifier (sur la base des informations contenues dans le fichier) l'identité de l'utilisateur. ItemsSource , DisplayMemberPath et ValueMemberPath . Les liens "DisplayMemberPath" et "ValueMemberPath" seraient les mêmes que ceux de l'option KeyValueControl .

Je ne sais pas si un contrôle d'utilisateur standard peut gérer cela, ou si je dois hériter d'un contrôle de type Selector .

Le XAML du contrôle ressemblerait à quelque chose comme ceci :

<Label Content={Binding Path=Name} /> 
<ComboBox SelectedValue={Binding Path=Value}
          ItemsSource={Binding [BOUND TO THE ItemsSource PROPERTY OF THIS CUSTOM CONTROL]
          DisplayMemberPath={Binding [BOUND TO THE DisplayMemberPath OF THIS CUSTOM CONTROL]
          SelectedValuePath={Binding [BOUND TO THE SelectedValuePath OF THIS CUSTOM CONTROL]/>

Dans mon code, je ferais alors quelque chose comme ceci (en supposant que ce nœud est une "chose" et doit afficher une liste de choses pour que l'utilisateur puisse sélectionner l'ID :

var myBoundComboBox = new KeyValueComboBox();
myBoundComboBox.ItemsSource = getThingsList();
myBoundComboBox.DisplayMemberPath = "ThingName";
myBoundComboBox.ValueMemberPath = "ThingID"
myBoundComboBox.DataContext = thisXElement;
...
myStackPanel.Children.Add(myBoundComboBox)

Mes questions sont donc les suivantes :

1) Devrais-je hériter de mon KeyValueComboBox de Control ou Selector ?

2) Si je dois hériter de Control comment puis-je exposer le contenu de la boîte de dialogue interne ? ItemsSource , DisplayMemberPath et ValueMemberPath pour la liaison ?

3) Si je dois hériter de Selector, quelqu'un peut-il me fournir un petit exemple de la façon dont je pourrais commencer à le faire ? Encore une fois, je suis novice en WPF, donc un exemple simple et agréable m'aiderait vraiment si c'est la voie que je dois suivre.

52voto

fbl Points 1058

J'ai fini par trouver comment faire par moi-même. Je publie la réponse ici pour que d'autres puissent voir une solution qui fonctionne, et peut-être qu'un gourou de WPF viendra me montrer une façon meilleure/plus élégante de le faire.

Donc, la réponse a fini par être le numéro 2. Exposer les propriétés internes s'avère être la bonne réponse. La mise en place est en fait assez facile une fois que vous savez comment faire. Il n'y a pas beaucoup d'exemples complets de ce genre (que j'ai pu trouver), alors j'espère que celui-ci aidera quelqu'un d'autre qui rencontre ce problème.

ComboBoxWithLabel.xaml.cs

L'élément important dans ce fichier est l'utilisation de DependencyProperties. Notez que tout ce que nous faisons pour l'instant est d'exposer les propriétés ( LabelContent et ItemsSource ). Le XAML se chargera de relier les propriétés du contrôle interne à ces propriétés externes.

namespace BoundComboBoxExample
{
    /// <summary>
    /// Interaction logic for ComboBoxWithLabel.xaml
    /// </summary>
    public partial class ComboBoxWithLabel : UserControl
    {
        // Declare ItemsSource and Register as an Owner of ComboBox.ItemsSource
        // the ComboBoxWithLabel.xaml will bind the ComboBox.ItemsSource to this
        // property
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
          ComboBox.ItemsSourceProperty.AddOwner(typeof(ComboBoxWithLabel));

        // Declare a new LabelContent property that can be bound as well
        // The ComboBoxWithLable.xaml will bind the Label's content to this
        public string LabelContent
        {
            get { return (string)GetValue(LabelContentProperty); }
            set { SetValue(LabelContentProperty, value); }
        }

        public static readonly DependencyProperty LabelContentProperty =
          DependencyProperty.Register("LabelContent", typeof(string), typeof(ComboBoxWithLabel));

        public ComboBoxWithLabel()
        {
            InitializeComponent();
        }
    }
}

ComboBoxWithLabel.xaml

Le langage XAML est assez simple, à l'exception des liens sur l'étiquette et sur l'onglet ComboBox ItemsSource . J'ai trouvé que la façon la plus simple d'obtenir ces liaisons est de déclarer les propriétés dans le fichier .cs (comme ci-dessus) et ensuite d'utiliser le concepteur VS2010 pour configurer la source de liaison à partir du panneau de propriétés. Essentiellement, c'est la seule façon que je connaisse pour lier les propriétés d'un contrôle interne au contrôle de base. S'il y a une meilleure façon de le faire, veuillez me le faire savoir.

<UserControl x:Class="BoundComboBoxExample.ComboBoxWithLabel"
             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="28" d:DesignWidth="453" xmlns:my="clr-namespace:BoundComboBoxExample">
    <Grid>
        <DockPanel LastChildFill="True">
            <!-- This will bind the Content property on the label to the 'LabelContent' 
                 property on this control-->
            <Label Content="{Binding Path=LabelContent, 
                             RelativeSource={RelativeSource FindAncestor, 
                                             AncestorType=my:ComboBoxWithLabel, 
                                             AncestorLevel=1}}" 
                   Width="100" 
                   HorizontalAlignment="Left"/>
            <!-- This will bind the ItemsSource of the ComboBox to this 
                 control's ItemsSource property -->
            <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, 
                                    AncestorType=my:ComboBoxWithLabel, 
                                    AncestorLevel=1}, 
                                    Path=ItemsSource}"></ComboBox>
            <!-- you can do the same thing with SelectedValuePath, 
                 DisplayMemberPath, etc, but this illustrates the technique -->
        </DockPanel>

    </Grid>
</UserControl>

MainWindow.xaml

Le XAML pour l'utiliser n'est pas intéressant du tout ce qui est exactement ce que je voulais. Vous pouvez définir le ItemsSource et le LabelContent via toutes les techniques standard de WPF.

<Window x:Class="BoundComboBoxExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="86" Width="464" xmlns:my="clr-namespace:BoundComboBoxExample"
        Loaded="Window_Loaded">
    <Window.Resources>
        <ObjectDataProvider x:Key="LookupValues" />
    </Window.Resources>
    <Grid>
        <my:ComboBoxWithLabel LabelContent="Foo"
                              ItemsSource="{Binding Source={StaticResource LookupValues}}"
                              HorizontalAlignment="Left" 
                              Margin="12,12,0,0" 
                              x:Name="comboBoxWithLabel1" 
                              VerticalAlignment="Top" 
                              Height="23" 
                              Width="418" />
    </Grid>
</Window>

Par souci d'exhaustivité, voici le fichier MainWindow.xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        ((ObjectDataProvider)FindResource("LookupValues")).ObjectInstance =
            (from i in Enumerable.Range(0, 5)
             select string.Format("Bar {0}", i)).ToArray();

    }
}

0 votes

J'utilise une technique similaire mais je rencontre des problèmes lorsque mes éléments sont configurés pour utiliser des liaisons RelativeSource. Ainsi, dans votre cas, si vous avez attribué une collection d'éléments ComboBoxItem comme ItemsSource et que l'un d'entre eux est lié à un élément situé plus haut dans l'arbre logique en utilisant la liaison RelativeSource à FindAncestor afin de se lier à une propriété définie sur cet élément ancêtre, la liaison échouera.

1 votes

Je viens de l'utiliser pour un cas très similaire, bonne réponse. Dans mon application, je me liais à l'élément sélectionné d'une boîte combinée. Il faut seulement noter que pour que la liaison fonctionne correctement, dans certains cas, il est nécessaire d'ajouter dans la liaison Mode = twoway et le UpdateSourceTrigger = PropertyChanged.

1voto

Jakub Pawlinski Points 154

J'ai essayé votre solution mais elle échoue pour moi. Elle ne transmet pas du tout la valeur au contrôle interne. Ce que j'ai fait, c'est déclarer les mêmes propriétés de dépendance dans le contrôle externe et lier le contrôle interne au contrôle externe de cette manière :

    // Declare IsReadOnly property and Register as an Owner of TimePicker (base InputBase).IsReadOnly the TimePickerEx.xaml will bind the TimePicker.IsReadOnly to this property
    // does not work: public static readonly DependencyProperty IsReadOnlyProperty = InputBase.IsReadOnlyProperty.AddOwner(typeof(TimePickerEx));

    public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof (bool), typeof (TimePickerEx), new PropertyMetadata(default(bool)));
    public bool IsReadOnly
    {
        get { return (bool) GetValue(IsReadOnlyProperty); }
        set { SetValue(IsReadOnlyProperty, value); }
    }

Que dans xaml :

  <UserControl x:Class="CBRControls.TimePickerEx" x:Name="TimePickerExControl"
        ...
        >

      <xctk:TimePicker x:Name="Picker" 
              IsReadOnly="{Binding ElementName=TimePickerExControl, Path=IsReadOnly}"
              ...
       />

  </UserControl>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X