2010-08-10 15 views
16

Tengo un problema simple en mi aplicación WPF que me tiene golpeando la cabeza en la mesa. Tengo un TabControl, donde cada TabItem es una vista generada por un modelo de vista usando un DataTemplate similar a esto:WPF ComboBox SelectedItem establecido en nulo en el conmutador TabControl

<DataTemplate DataType="{x:Type vm:FooViewModel}"> 
    <vw:FooView/> 
</DataTemplate> 

FooView contiene un ComboBox:

<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar}"/> 

y FooViewModel contiene una propiedad sencilla: public Bar SelectedBar { get; set; }. Mi problema es que cuando configuro el valor de mi ComboBox, cambio a otra pestaña y luego cambio, el ComboBox está vacío otra vez. Si configuro un punto de interrupción en el colocador para mi propiedad, veo que la propiedad está asignada al null cuando cambio a otra pestaña.

Por lo que entiendo, cuando se cambia una pestaña, se elimina del VisualTree, pero ¿por qué está configurando la propiedad de mi ViewModel en null? Esto hace que sea muy difícil para mí mantener un estado persistente, y consultar value != null no parece ser la solución correcta. ¿Alguien puede arrojar algo sobre esta situación?

Editar: La pila de llamadas en el punto de interrupción del ajustador solo muestra [Código externo] - no hay pistas allí.

+0

¿Ha marcado * en el código * que el elemento seleccionado se está configurando la primera vez?He tenido algunos casos en los que la selección es visible, pero el elemento seleccionado es== nulo, especialmente con las clases de SubSonic 3. – SteveCav

+0

Es una buena idea, pero el valor definitivamente se almacena la primera vez. Cuando rompo, puedo ver que 'value = null' y mi variable está almacenando el valor previamente seleccionado. – bsg

+0

¿Puedes mostrar la pila de llamadas para ese punto de interrupción? –

Respuesta

0

Una vez tuve un problema similar. Parece que el cuadro combinado pierde el elemento seleccionado en el evento VisibilityChanged. Workarround es despejar la unión antes de que esto ocurra, y reiniciarla cuando regrese. También puede tratar de establecer la unión a modo = TwoWay

la esperanza de que esto ayude a

Ene

2

Mi aplicación está utilizando avalondock & prims y tenía ese problema exacto. Tengo el mismo pensamiento con BSG, cuando cambiamos la pestaña o el contenido del documento en la aplicación MVVM, los controles como listview + box, el cuadro combinado se elimina de VisualTree. Me bugged y vi que la mayoría de los datos de ellos se restableció a nulo, como itemssource, selecteditem, .. pero selectedboxitem todavía tenía el valor actual.

Un enfoque es en el modelo, comprobar su valor es nulo luego regresar como esto:

private Employee _selectedEmployee; 
public Employee SelectedEmployee 
{ 
    get { return _selectedEmployee; } 
    set 
    { 
     if (_selectedEmployee == value || 
      IsAdding || 
      (value == null && Employees.Count > 0)) 
    { 
     return; 
    } 

    _selectedEmployee = value; 
    OnPropertyChanged(() => SelectedEmployee); 
} 

Pero este enfoque sólo puede resolver bastante bien en el primer nivel de consolidación. quiero decir, cómo vamos si queremos vincular a SelectedEmployee.Office a combobox, haga lo mismo no es bueno si marca el evento propertyChanged del modelo SelectedEmployee.

Básicamente, no queremos que su valor se restablezca nulo, conserve su valor previo. Encontré una nueva solución consistentemente. Mediante el uso de la propiedad adjunto, he creado un KeepSelection-Pro, tipo bool, para los controles de selección, así el suministro de toda su herencia como chupar vista de lista, cuadro combinado ...

public class SelectorBehavior 
{ 

public static bool GetKeepSelection(DependencyObject obj) 
{ 
    return (bool)obj.GetValue(KeepSelectionProperty); 
} 

public static void SetKeepSelection(DependencyObject obj, bool value) 
{ 
    obj.SetValue(KeepSelectionProperty, value); 
} 

// Using a DependencyProperty as the backing store for KeepSelection. This enables animation, styling, binding, etc... 
public static readonly DependencyProperty KeepSelectionProperty = 
    DependencyProperty.RegisterAttached("KeepSelection", typeof(bool), typeof(SelectorBehavior), 
    new UIPropertyMetadata(false, new PropertyChangedCallback(onKeepSelectionChanged))); 

static void onKeepSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    var selector = d as Selector; 
    var value = (bool)e.NewValue; 
    if (value) 
    { 
     selector.SelectionChanged += selector_SelectionChanged; 
    } 
    else 
    { 
     selector.SelectionChanged -= selector_SelectionChanged; 
    } 
} 

static void selector_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    var selector = sender as Selector; 

    if (e.RemovedItems.Count > 0) 
    { 
     var deselectedItem = e.RemovedItems[0]; 
     if (selector.SelectedItem == null) 
     { 
      selector.SelectedItem = deselectedItem; 
      e.Handled = true; 
     } 
    } 
} 
} 

final, yo uso este enfoque simplemente en XAML:

<ComboBox lsControl:SelectorBehavior.KeepSelection="true" 
     ItemsSource="{Binding Offices}" 
     SelectedItem="{Binding SelectedEmployee.Office}" 
     SelectedValuePath="Id" 
     DisplayMemberPath="Name"></ComboBox> 

Pero el elemento seleccionado nunca se anulará si los elementos del selector tienen elementos. Puede afectar algún contexto especial.

Espero que ayude. Feliz conding!: D

longsam

+1

Conding fue muy agradable. Gracias por la solución. – Gleno

+0

¡Cosas geniales! :) – Jefim

19

que acabo de encontrar con el mismo problema. Encontramos una entrada de blog que describe el problema. Parece que es un error en WPF y hay una solución: Especifique el enlace SelectedItem antes de el enlace ItemsSource y el problema debería desaparecer.

El enlace al artículo de blog:

http://www.metanous.be/pharcyde/post/Bug-in-WPF-combobox-databinding.aspx

+1

El enlace proporcionado no funciona – vzczc

+1

Aunque el enlace no funciona, el cambio sugerido SOLO resuelve el problema. –

+1

Esto solo parece solucionar el problema algunas veces –

1

Generalmente, utilizo SelectedValue en lugar de SelectedItem. Si necesito el objeto asociado con SelectedValue, agrego un campo de búsqueda que contiene esto al objeto de destino (como uso las plantillas T4 para generar mis viewmodels esto tiende a ser en una clase parcial). Si utiliza una propiedad que admite valores NULL para almacenar SelectedValue, experimentará el problema descrito anteriormente, sin embargo, si vincula el SelectedValue a un valor que no admite valores NULL (como un int), el motor de enlace WPF descartará el valor nulo como inadecuado para el objetivo.

1

Edit: Below stuff works (Espero ...); Lo desarrollé porque seguí la ruta SelectedItems descrita en la página MVVM Lite. Sin embargo, ¿por qué deseo confiar en SelectedItems? Agregar una propiedad IsSelected a Mis Artículos (como se muestra en here) conserva automáticamente los elementos seleccionados (excepto el mentioned cavet en el enlace de arriba). Al final, ¡mucho más fácil!

Publicación inicial: bien - eso fue un trabajo; Tengo un ListView de varias columnas con SelectionMode = "Extension", que hace que todo sea bastante complejo. Mi punto de partida es invocar tabItems desde espacios de trabajo similares a los descritos en here.

  1. Me aseguré de que en mi ViewModel, sé cuando un elemento de pestaña (área de trabajo) está activo. (Esto es un poco similar al here) - por supuesto, alguien necesita inicializar SelectedWorkspace primero.

    private Int32 _selectedWorkspace; 
    public Int32 SelectedWorkspace { 
        get { return _selectedWorkspace; } 
        set { 
        _selectedWorkspace = value; 
        base.OnPropertyChanged("SelectedWorkspace"); 
        } 
    } 
    protected Int32 _thisWorkspaceIdx = -1; 
    protected Int32 _oldSelectedWorkspace = -1; 
    public void OnSelectedWorkspaceChanged(object sender, PropertyChangedEventArgs e) { 
        if (e.PropertyName == "SelectedWorkspace") { 
        if (_oldSelectedWorkspace >= 0) { 
         Workspaces[_oldSelectedWorkpace].OnIsActivatedChanged(false); 
        } 
        Workspaces[SelectedWorkspace].OnIsActivatedChanged(true); 
        _oldSelectedWorkspace = SelectedWorkspace; 
        } 
    } 
    protected bool _isActive = false; 
    protected virtual void OnIsActivatedChanged(bool isActive) { 
        _isActive = isActive; 
    } 
    
  2. Esto me permitió actualizar los ViewModel elementos seleccionados solamente si el artículo pestaña (espacio de trabajo) era realmente activos. Por lo tanto, mi lista de elementos seleccionados de ViewModel se conserva incluso cuando el elemento de la pestaña borra ListView.SelectedItems. En el modelo de vista:

    if (_isActive) { 
        // ... update ViewModel selected items, referred below as vm.selectedItems 
    } 
    
  3. pasado, cuando consiguió volver a habilitar el TabItem, me puse en contacto con el evento 'Loaded' y restauró la SelectedItems. Esto se hace en el código subyacente de la Vista. (Tenga en cuenta que, si bien mi ListView tiene varias columnas, una sirve como clave, las otras son solo para información. La lista ViewModel selectedItems solo conserva la clave.Si no, la siguiente comparación sería más complejo):

    private void myList_Loaded(object sender, RoutedEventArgs e) { 
        myViewModel vm = DataContext as myViewModel; 
        if (vm.selectedItems.Count > 0) { 
        foreach (string myKey in vm.selectedItems) { 
         foreach (var item in myList.Items) { 
         MyViewModel.MyItem i = item as MyViewModel.MyItem; 
         if (i.Key == myKey) { 
          myList.SelectedItems.Add(item); 
         } 
         } 
        } 
        } 
    } 
    
1

si demandar a la selección asíncrono en WPF luego lo elimina IsSynchronizedWithCurrentItem = "true" desde el cuadro combinado, consulte el documento sobre IsSynchronizedWithCurrentItem :

<ComboBox 
    Name="tmpName" 
    Grid.Row="10" 
    Width="250" 
    Text="Best Match Position List" 
    HorizontalAlignment="Left" 
    Margin="14,0,0,0" 

    SelectedItem="{Binding Path=selectedSurceList,Mode=TwoWay}" 
    ItemsSource="{Binding Path=abcList}" 
    DisplayMemberPath="Name" 
    SelectedValuePath="Code" 
    IsEnabled="{Binding ElementName=UserBestMatchYesRadioBtn,Path=IsChecked}"> 
</ComboBox> 

también takecare la primer uso de unión SelectedItem entonces ItemsSource

ref: http://social.msdn.microsoft.com/Forums/vstudio/en-US/fb8a8ad2-83c1-43df-b3c9-61353979d3d7/comboboxselectedvalue-is-lost-when-itemssource-is-updated?forum=wpf

http://social.msdn.microsoft.com/Forums/en-US/c9e62ad7-926e-4612-8b0c-cc75fbd160fd/bug-in-wpf-combobox-data-binding

que resolver mi problema con lo anterior

0

que tenía el mismo problema y lo resolvió con el siguiente método asociado a la Combobox DataContextChanged-Evento:

private void myCombobox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
{ 
    if (sender is FrameworkElement && e.NewValue == null) 
     ((FrameworkElement)sender).DataContext = e.OldValue; 
} 

Así que cada vez que quiera eliminar el contexto de datos del cuadro combinado, se volverá a establecer el antiguo contexto de datos.

Cada vez que cambie la pestaña activa de su TabControl, el Combobox se eliminará de su VisualTree y se agregará si vuelve al que tiene su cuadro combinado. Si el cuadro combinado se elimina del VisualTree, también DataContext se establece en nulo.

o bien utilizar una clase, que han puesto en práctica de estas características:

public class MyCombobox : ComboBox 
{ 
    public MyCombobox() 
    { 
     this.DataContextChanged += MyCombobox_DataContextChanged; 
    } 

    void MyCombobox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) 
    { 
     if (sender is FrameworkElement && e.NewValue == null) 
      ((FrameworkElement)sender).DataContext = e.OldValue; 
    } 
    public void SetDataContextExplicit(object dataContext) 
    { 
     lock(this.DataContext) 
     { 
      this.DataContextChanged -= MyCombobox_DataContextChanged; 
      this.DataContext = dataContext; 
      this.DataContextChanged += MyCombobox_DataContextChanged; 
     } 
    } 
} 
0

Creo que el problema puede ser que te enviaban contando el cuadro combinado cuando se una de nuevo a la fuente. Prueba esto:

<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar, UpdateSourceTrigger=PropertyChanged}"/ 
0

tenía este mismo problema cuando se desplaza a través de una virtualización de DataGrid que contiene ComboBox es. El uso de IsSynchronizedWithCurrentItem no funcionó, ni cambió el orden de las vinculaciones SelectedItem y ItemsSource. Pero aquí hay un hack feo que parece funcionar:

Primero, dele a su ComboBox un x:Name. Esto debería estar en el XAML para un control con un solo ComboBox. Por ejemplo:

<ComboBox x:Name="mComboBox" SelectedItem="{Binding SelectedTarget.WritableData, Mode=TwoWay}"> 

A continuación, agregue estos dos controladores de eventos en su código subyacente:

using System.Windows.Controls; 
using System.Windows; 

namespace SATS.FileParsing.UserLogic 
{ 
    public partial class VariableTargetSelector : UserControl 
    { 
     public VariableTargetSelector() 
     { 
      InitializeComponent(); 
      mComboBox.DataContextChanged += mComboBox_DataContextChanged; 
      mComboBox.SelectionChanged += mComboBox_SelectionChanged; 
     } 

     /// <summary> 
     /// Without this, if you grab the scrollbar and frantically scroll around, some ComboBoxes get their SelectedItem set to null. 
     /// Don't ask me why. 
     /// </summary> 
     void mComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
     { 
      mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateTarget(); 
     } 

     /// <summary> 
     /// Without this, picking a new item in the dropdown does not update IVariablePair.SelectedTarget.WritableData. 
     /// Don't ask me why. 
     /// </summary> 
     void mComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
     { 
      mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateSource(); 
     } 
    } 
} 
0

Puede utilizar el marco MVVM Catel y la Catel: Elemento TabControl existe este problema ya está resuelto.

0

Simplemente no permita que se cambie la propiedad de su ViewModel si el valor se vuelve nulo.

public Bar SelectedBar 
{ 
    get { return barSelected; } 
    set { if (value != null) SetProperty(ref barSelected, value); } 
} 

Eso es todo.