Exposition des propriétés de contrôle interne pour la liaison dans WPF

[Edit]: j'ai compris comment le faire par moi-même. J'ai posté ma solution dans l'espoir qu'elle sauvera quelqu'un d'autre quelques jours de googling. Si vous êtes un gourou WPF, veuillez regarder ma solution et laissez-moi savoir s'il existe un moyen meilleur / plus élégant / plus efficace de le faire. En particulier, je suis intéressé à savoir ce que je ne sais pas... comment cette solution va-t-elle me foutre en l'air? Le problème se résume vraiment à exposer le contrôle interne propriété.

Problème: Je crée du code pour générer automatiquement une interface graphique liée aux données dans WPF pour un fichier XML. J'ai un fichier xsd qui peut m'aider à déterminer les types de nœuds, etc. Les éléments clés/valeurs simples sont faciles.

Quand j'analyse cet élément:

<Key>value</Key>

Je peux créer un nouveau 'KeyValueControl' et définir le DataContext à cet élément. Le KeyValue est défini comme un UserControl et a juste quelques liaisons simples dessus. 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 a une étiquette avec le nom de l'élément et une zone de texte avec la valeur que je peux modifier.

Maintenant, il y a des moments où j'ai besoin d'Afficher des valeurs de recherche au lieu de la valeur réelle. Je voudrais créer un 'KeyValueComboBox' similaire au KeyValueControl ci-dessus mais être capable de spécifier (en fonction des informations dans le fichier) les chemins ItemsSource, Display et Value. Le 'Nom' et 'Valeur' liaisons seraient les mêmes que le KeyValueControl.

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

Le XAML dans le contrôle ressemblerait à 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)

, Donc mes questions sont:

1) Dois-je hériter de mon KeyValueComboBox de Control ou Le sélecteur?

2) Si je dois hériter de Control, Comment puis-je exposer ItemsSource, DisplayMemberPath et ValueMemberPath de la boîte de liste déroulante interne pour la liaison?

3) Si j'ai besoin d'hériter de Selector, quelqu'un peut-il fournir un petit exemple de la façon dont je pourrais commencer avec cela? Encore une fois, je suis nouveau sur WPF, donc un exemple simple et agréable aiderait vraiment si c'est la route que je dois prendre.

39
demandé sur Dave Clemmer 2010-11-13 00:15:07

2 réponses

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

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

ComboBoxWithLabel.XAML.cs

La chose importante dans ce fichier est L'utilisation de DependencyProperties. Notez que tout ce que nous faisons en ce moment est simplement d'exposer les propriétés (LabelContent et ItemsSource). Le XAML s'occupera du câblage des propriétés du contrôle interne à ces propriétés externes.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;

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 Xaml est assez simple, à l'exception du liaisons sur L'étiquette et la liste déroulante ItemsSource. J'ai trouvé que le moyen le plus simple d'obtenir ces liaisons est de déclarer les propriétés dans le .fichier cs (comme ci-dessus), puis utilisez le concepteur VS2010 pour configurer la source de liaison à partir du volet Propriétés. Essentiellement, c'est la seule façon que je connaisse de 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, faites-le moi 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>

Fenêtre principale.xaml

Le XAML à utiliser n'est pas intéressant à tous.. ce qui est exactement ce que je voulais. Vous pouvez définir ItemsSource et LabelContent via toutes les techniques WPF standard.

<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 la 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();

    }
}
48
répondu fbl 2010-11-14 05:14:23

J'ai essayé votre solution mais elle échoue pour moi. Il ne transmet pas du tout la valeur au contrôle interne. Ce que j'ai fait est une déclaration des mêmes propriétés de dépendance dans le contrôle externe et liée à l'intérieur à l'extérieur comme ça:

    // 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>
1
répondu Jakub Pawlinski 2015-12-03 11:13:15