2011-03-01 34 views
5

Estoy tratando de implementar una selección de cuadro combinado con CollectionViewSource. Este cuadro combinado es en realidad parte de una plantilla de datos y se repite en una vista de lista. Mi primer enfoque pareció funcionar (utilizando CollectionViewSource) pero todos mis cuadros combinados compartían el mismo contexto de datos. Esto hizo que cada vez que se cambiara una de las otras cajas, todas las demás cambiaran para reflejar, no un efecto secundario deseado.Encuadernación con CollectionViewSource

Decidí simplemente retroceder e intentar implementar un cuadro combinado básico (no dentro de una plantilla de datos) usando xaml en línea para especificar CollectionViewSource (en lugar de crear los cvs como un recurso estático). No he podido mostrar mis datos con éxito. Probablemente estoy haciendo esto completamente mal ya que todavía soy nuevo en WPF.

Aquí está el XAML para mi cuadro combinado:

<ComboBox> 
    <ComboBox.ItemsSource> 
     <Binding> 
      <Binding.Source> 
       <CollectionViewSource Source="{Binding Path=Configurations}"> 
        <CollectionViewSource.SortDescriptions> 
         <scm:SortDescription PropertyName="AgencyName" /> 
        </CollectionViewSource.SortDescriptions> 
       </CollectionViewSource> 
      </Binding.Source> 
     </Binding> 
    </ComboBox.ItemsSource> 
</ComboBox> 

El DataContext del control de usuario, donde vive está ligado a un objeto que tiene una ObservableCollection llamada Configuraciones este cuadro combinado y cada configuración tiene una propiedad llamada AgencyName . He verificado que esto funciona bien usando enlaces estándar sin los cvs, así que sé que todo está bien en ese acuerdo.

Cualquier ayuda sería muy apreciada ya que me he quedado sin excusas para mi jefe :). Tampoco quiero tener que desplegarme en el código y hacer la clasificación en el código subyacente (que podría cuando construyo el ObservableCollection pero en mi humilde opinión que viola el principio DRY).

Respuesta

3

¿Qué es exactamente lo que quiere decir con "cada vez que una de las otras cajas se cambió todos los demás han cambiado a reflejar"? ¿Estás hablando del elemento seleccionado? Si es así, puede ayudar a configurar IsSynchronizedWithCurrentItem = false en su ComboBox.

Además de eso: creo que siempre que cree y clasifique su ICollectionView en el código de detrás una sola vez, no hay violación del principio DRY, porque lo que hace más allí ya no es necesario en el XAML. Pero veo que puede haber otras razones para decir que una característica como la clasificación debe hacerse en la Vista, hablando en términos de Model-View-ViewModel.

+0

Ah IsSynchronizedWithCurrentItem funciona como un amuleto. No se sincroniza con la lista y no impide el enlace a una propiedad en ModelView. Acabo de terminar de verificar eso. Resuelve mi problema con elegancia. No puedo pedir más, gracias. – SRM

+0

Eres el hombre, pensé que me topaba con una limitación de WPF que requería el uso de una copia de toda la fuente de visión para el engendro de cada plantilla. Sería difícil incluso google/describir el problema. –

+0

PD. Es bastante extraño que este sea el comportamiento predeterminado de WPF, o al menos predeterminado en las plantillas, pero bueno. –

1

No leí su publicación completa, pero el problema es que los recursos se comparten de forma predeterminada. Por lo tanto, cada cuadro combinado hacía referencia a la misma vista de colección. Una vista de colección incluye la selección de seguimiento, por lo que cambiar la selección en un cuadro combinado afectaría a los demás.

En lugar de mover el CVS a un recurso local, que sólo podría evitar que sea compartida:

<CollectionViewSource x:Key="whatever" x:Shared="False" .../> 
+0

Eso parece prometedor. Voy a dar una oportunidad (ya que mis cvs funcionaban perfectamente, aparte del aspecto compartido). ¡Gracias! – SRM

+0

Bueno, eso parecía prometedor pero no parecía funcionar. Agregué el atributo x: Shared (que no apareció en intelisense por algún motivo) y los cuadros combinados dejaron de representar cualquier contenido. Cambie la bandera a True y todo funciona bien – SRM

0

Aunque es probable que sea demasiado tarde, dejaré esta respuesta para los demás que puedan encontrar este problema. Su enlace para CollectionViewSource.Source no funciona porque CollectionViewSource no pertenece al árbol visual/lógico, y tampoco hereda el Contexto de datos ni puede hacer referencia al ComboBox como fuente de enlace. Yo era capaz de solucionar esto de una manera fea, pero simple uso de la clase siguiente:

/// <summary> 
/// Provides a way to set binding between a control 
/// and an object which is not part of the visual tree. 
/// </summary> 
/// <remarks> 
/// A bright example when you need this class is having an 
/// <see cref="ItemsControl"/> bound to a <see cref="CollectionViewSource"/>. 
/// The tricky thing arises when you want the <see cref="CollectionViewSource.Source"/> 
/// to be bound to some property of the <see cref="ItemsControl"/> 
/// (e.g. to its data context, and to the view model). Since 
/// <see cref="CollectionViewSource"/> doesn't belong to the visual/logical tree, 
/// its not able to reference the <see cref="ItemsControl"/>. To stay in markup, 
/// you do the following: 
/// 1) Add an instance of the <see cref="BindingBridge"/> to the resources 
/// of some parent element; 
/// 2) On the <see cref="ItemsControl"/> set the <see cref="BindingBridge.BridgeInstance"/> attached property to the 
/// instance created on step 1) using <see cref="StaticResourceExtension"/>; 
/// 3) Set the <see cref="CollectionViewSource.Source"/> to a binding which has 
/// source set (via <see cref="StaticResourceExtension"/>) to <see cref="BindingBridge"/> 
/// and path set to the <see cref="BindingBridge.SourceElement"/> (which will be the control 
/// on which you set the attached property on step 2) plus the property of interest 
/// (e.g. <see cref="FrameworkElement.DataContext"/>): 
/// <code> 
/// <CollectionViewSource 
///  Source="{Binding SourceElement.DataContext.Images, Source={StaticResource ImagesBindingBridge}}"/> 
/// </code>. 
/// 
/// So the result is that when assigning the attached property on a control, the assigned 
/// <see cref="BindingBridge"/> stores the reference to the control. And that reference can be 
/// retrieved from the <see cref="BindingBridge.SourceElement"/>. 
/// </remarks> 
public sealed class BindingBridge : DependencyObject 
{ 
    #region BridgeInstance property 

    public static BindingBridge GetBridgeInstance(DependencyObject obj) 
    { 
     Contract.Requires(obj != null); 
     return (BindingBridge)obj.GetValue(BridgeInstanceProperty); 
    } 

    public static void SetBridgeInstance(DependencyObject obj, BindingBridge value) 
    { 
     Contract.Requires(obj != null); 
     obj.SetValue(BridgeInstanceProperty, value); 
    } 

    // Using a DependencyProperty as the backing store for BridgeInstance. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty BridgeInstanceProperty = 
     DependencyProperty.RegisterAttached("BridgeInstance", typeof(BindingBridge), typeof(BindingBridge), 
     new PropertyMetadata(OnBridgeInstancePropertyChanged)); 

    #endregion BridgeInstance property 

    #region SourceElement property 

    public FrameworkElement SourceElement 
    { 
     get { return (FrameworkElement)GetValue(SourceElementProperty); } 
     private set { SetValue(SourceElementPropertyKey, value); } 
    } 

    // Using a DependencyProperty as the backing store for SourceElement. This enables animation, styling, binding, etc... 
    private static readonly DependencyPropertyKey SourceElementPropertyKey = 
     DependencyProperty.RegisterReadOnly("SourceElement", typeof(FrameworkElement), typeof(BindingBridge), new PropertyMetadata(null)); 

    public static readonly DependencyProperty SourceElementProperty; 

    #endregion SourceElement property 

    /// <summary> 
    /// Initializes the <see cref="BindingBridge"/> class. 
    /// </summary> 
    static BindingBridge() 
    { 
     SourceElementProperty = SourceElementPropertyKey.DependencyProperty; 
    } 

    private static void OnBridgeInstancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var sourceElement = (FrameworkElement)d; 
     var bridge = (BindingBridge)e.NewValue; 
     bridge.SourceElement = sourceElement; 
    } 
} 

Aquí es un ejemplo de uso (no se muestra el diccionario de recursos):

<ItemsControl 
     infrastructure:BindingBridge.BridgeInstance="{StaticResource ImagesBindingBridge}"> 
     <ItemsControl.ItemsSource> 
      <Binding> 
       <Binding.Source> 
        <CollectionViewSource 
           Source="{Binding SourceElement.DataContext.Images, Source={StaticResource ImagesBindingBridge}, Mode=OneWay}"> 
         <CollectionViewSource.SortDescriptions> 
          <componentModel:SortDescription PropertyName="Timestamp" Direction="Descending"/> 
         </CollectionViewSource.SortDescriptions> 
        </CollectionViewSource> 
       </Binding.Source> 
      </Binding> 
     </ItemsControl.ItemsSource> 
    </ItemsControl> 
0

depende de encuadernación en VisualTree, que cvs no es visual, por lo que el enlace no funciona.

Puede usar x: Reference en su lugar.

<Border x:Name="border" /> 
<ComboBox> 
    <ComboBox.ItemsSource> 
     <Binding> 
      <Binding.Source> 
       <CollectionViewSource Source="{Binding Path=DataContext.Configurations, Source={x:Reference border}}"> 
        <CollectionViewSource.SortDescriptions> 
         <scm:SortDescription PropertyName="AgencyName" /> 
        </CollectionViewSource.SortDescriptions> 
       </CollectionViewSource> 
      </Binding.Source> 
     </Binding> 
    </ComboBox.ItemsSource> 
</ComboBox>