2010-11-12 11 views
39

[Editar]: He descubierto cómo hacerlo yo solo. Publiqué mi solución con la esperanza de que le ahorre a alguien más unos pocos días de búsqueda de Google. Si eres un gurú de WPF, mira mi solución y avísame si hay una forma mejor/más elegante/más eficiente de hacerlo. En particular, estoy interesado en saber lo que no sé ... ¿cómo va esta solución a arruinarme el camino? El problema realmente se reduce a exponer las propiedades de control interno.Exponer propiedades de control interno para vincular en WPF

Problema: Estoy creando un código para autogenerar una GUI enlazada a datos en WPF para un archivo XML. Tengo un archivo xsd que puede ayudarme a determinar los tipos de nodo, etc. Los elementos simples de clave/valor son fáciles.

Cuando analizar este elemento:

<Key>value</Key> 

puedo crear un nuevo 'KeyValueControl' y establecer el DataContext a este elemento. KeyValue se define como UserControl y solo tiene algunos enlaces simples. Funciona muy bien para cualquier XElement simple.

El XAML dentro de este control es el siguiente:

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

El resultado es una línea que tiene una etiqueta con el nombre del elemento y un cuadro de texto con el valor que pueda editar.

Ahora, hay ocasiones en las que necesito mostrar los valores de búsqueda en lugar del valor real. Me gustaría crear un 'KeyValueComboBox' similar al KeyValueControl anterior pero poder especificar (basado en la información del archivo) las rutas de ItemsSource, Display y Value. Los enlaces 'Nombre' y 'Valor' serían los mismos que KeyValueControl.

No sé si un control de usuario estándar puede manejar esto, o si necesito heredar de Selector.

El XAML en el control sería algo como esto:

<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]/> 

En mi código, yo luego hacer algo como esto (suponiendo que este nodo es una 'cosa' y necesita mostrar una lista de cosas que el usuario puede seleccionar el ID:

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

Así que mis preguntas son:

1) ¿Debo heredar mi KeyValueComboBox de control o la de selección?

2) Si debo heredar de Control, ¿cómo exponer ItemsSource, DisplayMemberPath, y ValueMemberPath del Combo Box interno para el enlace?

3) Si necesito heredar de Selector, ¿alguien puede darme un pequeño ejemplo de cómo podría comenzar con eso? De nuevo, soy nuevo en WPF, por lo que un ejemplo simple y agradable sería de gran ayuda si ese es el camino que debo tomar.

Respuesta

47

Terminé pensando cómo hacerlo por mi cuenta. Estoy publicando la respuesta aquí para que otros puedan ver una solución que funcione, y tal vez un gurú de WPF vendrá y me mostrará una forma mejor/más elegante para hacer esto.

Entonces, la respuesta terminó siendo # 2. Exponer las propiedades internas resulta ser la respuesta correcta. Configurarlo es realmente bastante fácil ... una vez que sabes cómo hacerlo.No hay muchos ejemplos completos de esto (que pueda encontrar), así que espero que éste ayude a alguien más que se encuentre con este problema.

ComboBoxWithLabel.xaml.cs

Lo importante en este archivo es el uso de DependencyProperties. Tenga en cuenta que todo lo que estamos haciendo ahora es simplemente exponer las propiedades (LabelContent y ItemsSource). El XAML se encargará de cablear las propiedades del control interno a estas propiedades externas.

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

el XAML es bastante sencillo, con la excepción de los enlaces en la etiqueta y el cuadro combinado ItemsSource. Descubrí que la forma más sencilla de obtener estos enlaces es declarar las propiedades en el archivo .cs (como se indicó anteriormente) y luego usar el diseñador de VS2010 para configurar el origen de enlace desde el panel de propiedades. Básicamente, esta es la única forma que conozco de unir las propiedades de un control interno al control base. Si hay una mejor manera de hacerlo, házmelo saber.

<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

El XAML para utilizar este no es interesante en todos los .. que es exactamente lo que quería. Puede establecer ItemsSource y LabelContent a través de todas las técnicas estándar 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> 

para la buena integridad, aquí está el 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

estoy usando una técnica similar pero meterse en problemas cuando mi los elementos mismos están configurados para usar enlaces RelativeSource. Entonces en su caso si asignó una colección de elementos ComboBoxItem como su ItemsSource y uno tiene un enlace a algo en el árbol lógico usando el enlace RelativeSource a FindAncestor para enlazar a una propiedad definida en ese elemento anscestor entonces el enlace fallará. – jpierson

+0

Acabo de usar esto para un caso muy similar, buena respuesta. En mi Aplicación, me estaba vinculando al SelectedItem de un Combobox. Solo se debe tener en cuenta que para que el enlace funcione correctamente, en algunos casos es necesario agregar el modo de enlace = twoway y UpdateSourceTrigger = PropertyChanged – Cocomico

0

que probé su solución, pero no para mí. No pasa el valor al control interno en absoluto. Lo que hice es la declaración de los mismos en las propiedades de dependencia de control externa y con destino interno a externo así:

// 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 en xaml:

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

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

    </UserControl> 
Cuestiones relacionadas