web-dev-qa-db-fra.com

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

[[EDIT]: J'ai compris comment faire cela seul. J'ai posté ma solution dans l'espoir que cela 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 y a 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 vider la route? le problème se résume vraiment pour exposer les propriétés de la commande intérieure.

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

Quand j'étilise cet élément:

<Key>value</Key>

Je peux créer un nouveau "keyvaluecontrol" et définir le DataContext sur cet élément. Le keyvalue est défini comme un Usercontrol et a simplement des liaisons simples à ce sujet. Cela fonctionne parfaitement pour n'importe quel 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 d'élément et une zone de texte avec la valeur que je peux éditer.

Maintenant, il y a des moments où j'ai besoin d'afficher des valeurs de recherche au lieu de la valeur réelle. Je souhaite créer un "keyvaluecombockox" similaire à celui ci-dessus KeyValuecontrol, mais que vous pourrez spécifier (basé sur des informations dans le fichier), les itemsSource, les chemins d'affichage et de la valeur. Les liaisons "nom" et "valeur" seraient les mêmes que le keyvaluecontrol.

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

Le XAML dans le contrôle ressemblerait à quelque chose comme ça:

<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 ça (en supposant que ce nœud est une "chose" et doit afficher une liste de choses afin 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 contrôle ou de sélecteur?

2) Si je devrais hériter d'un contrôle, comment puis-je exposer les éléments de la boîte intérieure de la boîte déroulante, DisplayMemberPath et l'appréciation de la boîte à combo pour une liaison?

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

42
fbl

J'ai fini par comprendre comment faire cela seul. Je pose la réponse ici pour que d'autres puissent voir une solution qui fonctionne, et peut-être qu'un gourou du WPF viendra et me montrera un moyen de faire une meilleure/plus élégante pour le faire.

Donc, la réponse a fini par être n ° 2. L'exposition des propriétés intérieures 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 ceci (que je pouvais trouver), alors j'espère que celui-ci aidera quelqu'un d'autre qui traverse ce problème.

Comboboxwithlabel.xaml.cs

La chose importante dans ce fichier est l'utilisation de dépendancesProperties. Notez que tout ce que nous faisons en ce moment ne fait que l'exposition des propriétés (LabelContent et les itemsource). Le XAML s'occupera de câblage des propriétés du contrôle interne sur 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 des fixations de l'étiquette et des éléments de ComboBox. J'ai constaté que le moyen le plus simple d'obtenir ces liaisons est de déclarer les propriétés du fichier .Cs (comme ci-dessus), puis utilisez le concepteur VS2010 pour configurer la source de liaison du volet Propriétés. Essentiellement, c'est le seul moyen que je connaisse de lier les propriétés de la commande interne au contrôle de base. S'il y a une meilleure façon de le faire, merci de 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 à utiliser ce n'est pas intéressant du tout. C'est ce que je voulais exactement. Vous pouvez définir l'itemSource et le labelContent via toutes les techniques Standard 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>

Pour l'ensemble des sceptimes, voici le 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();

    }
}
49
fbl

J'ai essayé votre solution mais ça échoue pour moi. Il ne transmet pas la valeur à la commande intérieure du tout. Ce que j'ai fait est la déclaration des mêmes propriétés de dépendance dans la commande extérieure et lié à l'intérieur à l'extérieur comme celui-là:

    // 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
Jakub Pawlinski