Exponer las propiedades de control interno para el enlace en WPF
binding user-controls (2)
[Editar]: descubrí cómo hacer esto por mi cuenta. 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.
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 analizo este elemento:
<Key>value</Key>
Puedo crear un nuevo ''KeyValueControl'' y configurar DataContext
para 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 se ve así:
<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 puedo editar.
Ahora, hay ocasiones en las que necesito mostrar 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 se vería así:
<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, entonces haría algo como esto (suponiendo que este nodo es un ''Cosa'' y necesita mostrar una lista de Cosas para que el usuario pueda seleccionar la ID:
var myBoundComboBox = new KeyValueComboBox();
myBoundComboBox.ItemsSource = getThingsList();
myBoundComboBox.DisplayMemberPath = "ThingName";
myBoundComboBox.ValueMemberPath = "ThingID"
myBoundComboBox.DataContext = thisXElement;
...
myStackPanel.Children.Add(myBoundComboBox)
Entonces mis preguntas son:
1) ¿Debo heredar mi KeyValueComboBox de Control o Selector?
2) Si debería heredar de Control, ¿cómo expongo los ItemsSource, DisplayMemberPath y ValueMemberPath del cuadro combinado para el enlace?
3) Si necesito heredar de Selector, ¿alguien puede dar 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.
Intenté tu solución pero falla para mí. No pasa el valor al control interno en absoluto. Lo que hice fue la declaración de las mismas propiedades de dependencia en el control externo y ligadas de adentro hacia afuera 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>
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 ComboBox 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 usar esto no es para nada interesante ... que es exactamente lo que yo 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 Completeness Sake, 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();
}
}