wpf - que - ¿Por qué no puedo seleccionar un valor nulo en un ComboBox?
eventos combobox c# (10)
En WPF, parece ser imposible seleccionar (con el mouse) un valor "nulo" de un ComboBox. Editar Para aclarar, esto es .NET 3.5 SP1.
Aquí hay un código para mostrar lo que quiero decir. Primero, las declaraciones de C #:
public class Foo
{
public Bar Bar { get; set; }
}
public class Bar
{
public string Name { get; set; }
}
A continuación, mi Window1 XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox x:Name="bars"
DisplayMemberPath="Name"
Height="21"
SelectedItem="{Binding Bar}"
/>
</StackPanel>
</Window>
Y, por último, mi clase Window1:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
bars.ItemsSource = new ObservableCollection<Bar>
{
null,
new Bar { Name = "Hello" },
new Bar { Name = "World" }
};
this.DataContext = new Foo();
}
}
¿Conmigo? Tengo un ComboBox cuyos elementos están vinculados a una lista de instancias de Bar, una de las cuales es nula. He vinculado la ventana a una instancia de Foo, y el ComboBox muestra el valor de su propiedad de barra.
Cuando ejecuto esta aplicación, el ComboBox comienza con una pantalla vacía porque Foo.Bar es nulo por defecto. Esta bien. Si uso el mouse para soltar el ComboBox y seleccionar el ítem "Hola", eso también funciona. ¡Pero si trato de volver a seleccionar el artículo vacío en la parte superior de la lista, el ComboBox se cierra y vuelve a su valor anterior de "Hola"!
La selección del valor nulo con las teclas de flecha funciona como se esperaba, y su configuración programática también funciona. Solo se selecciona con un mouse que no funciona.
Sé que una solución fácil es tener una instancia de Bar que represente null y ejecutarla a través de IValueConverter, pero ¿alguien puede explicar por qué la selección de null con el mouse no funciona en ComboBox de WPF?
Bueno, recientemente encontré el mismo problema con valor nulo para ComboBox . Lo he solucionado usando dos convertidores:
Para la propiedad ItemsSource : reemplaza los valores nulos en la colección por cualquier valor pasado dentro del parámetro del convertidor:
class EnumerableNullReplaceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var collection = (IEnumerable)value; return collection .Cast<object>() .Select(x => x ?? parameter) .ToArray(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
Para propiedad SelectedValue : este hace lo mismo pero para el valor único y de dos maneras:
class NullReplaceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value ?? parameter; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value.Equals(parameter) ? null : value; } }
Ejemplo de uso:
<ComboBox
ItemsSource="{Binding MyValues, Converter={StaticResource EnumerableNullReplaceConverter}, ConverterParameter=''(Empty)''}"
SelectedValue="{Binding SelectedMyValue, Converter={StaticResource NullReplaceConverter}, ConverterParameter=''(Empty)''}"
/>
Resultado:
Nota: Si se une a ObservableCollection , perderá las notificaciones de cambio. Además, no desea tener más de un valor nulo en la colección.
ComboBox necesita una plantilla de datos para mostrar el elemento sin importar cuán simple sea. DataTemplate funciona así: obtener un valor de instancia. [Ruta], por ejemplo
bar1.Car.Color
Entonces no puede obtener un valor de
null.Car.Color
Lanzará una excepción de referencia nula. Por lo tanto, la instancia nula no se mostrará. Pero el Color - si es un tipo de referencia - se permite que sea nulo porque no habrá excepciones en este caso.
Pasé un día buscando una solución sobre este problema de seleccionar un valor nulo en el cuadro combinado y, finalmente, finalmente encontré una solución en un artículo escrito en esta url:
public class ComboBoxEmptyItemConverter : IValueConverter
{
/// <summary>
/// this object is the empty item in the combobox. A dynamic object that
/// returns null for all property request.
/// </summary>
private class EmptyItem : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// just set the result to null and return true
result = null;
return true;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// assume that the value at least inherits from IEnumerable
// otherwise we cannot use it.
IEnumerable container = value as IEnumerable;
if (container != null)
{
// everything inherits from object, so we can safely create a generic IEnumerable
IEnumerable<object> genericContainer = container.OfType<object>();
// create an array with a single EmptyItem object that serves to show en empty line
IEnumerable<object> emptyItem = new object[] { new EmptyItem() };
// use Linq to concatenate the two enumerable
return emptyItem.Concat(genericContainer);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<ComboBox ItemsSource="{Binding TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}"
SelectedValue="{Binding SelectedID}"
SelectedValuePath="ID"
DisplayMemberPath="Name" />
Prueba Binding.FallbackValue
De 6 cosas que apostaba que no sabías sobre la vinculación de datos en WPF
Puede que esto no aborde tu respuesta por completo, pero con suerte es un éxito en la dirección correcta:
- ¿Has instalado SP1?
- NET 3.5 SP1 incluye varias mejoras de edición y enlace de datos para
WPF. Éstas incluyen:- Compatibilidad con StringFormat dentro de las expresiones {{Binding}} para permitir un formato fácil de los valores encuadernados
- Nuevo soporte de filas alternadas dentro de los controles derivados de ItemsControl, lo que hace que sea más fácil establecer propiedades alternas en las filas (por ejemplo, alternar los colores de fondo)
- Mejor manejo y soporte de conversión para valores nulos en controles editables. Validación a nivel de artículo que aplica reglas de validación a un ítem entero encuadernado.
- Soporte de MultiSelector para manejar escenarios de selección múltiple y edición masiva
- IEditableCollectionView es compatible con controles de datos de interfaz a fuentes de datos y permite editar / agregar / eliminar elementos de forma transaccional
- Mejoras de rendimiento cuando se vincula a fuentes de datos IEnumerable
Lo siento si perdí tu tiempo y esto ni siquiera estuvo cerca ... pero creo que el problema se hereda de:
restricciones del conjunto de datos fuertemente tipado
NullValueDataSet Explicado aquí
Pero ahora el SP1 para .Net 3.5 debería haber abordado este problema.
Sé que esta respuesta no es la que pediste (una explicación de por qué no funciona con el mouse), pero creo que la premisa es defectuosa:
Desde mi punto de vista como programador y usuario (no .NET), seleccionar un valor nulo es algo malo. "nulo" se supone que es la ausencia de un valor, no algo que seleccione.
Si necesita la capacidad explícita de no seleccionar algo, le sugiero cualquiera de las soluciones que mencionó ("-", "na" o "none" como valor), o mejor
- Envuelva el cuadro combinado con una casilla de verificación que se puede desmarcar para deshabilitar el cuadro combinado. Esto me parece el diseño más limpio desde la perspectiva del usuario y programáticamente.
Solo una conjetura, pero creo que suena razonable.
Supongamos que combobox usa "ListCollectionView" (lcv como su instancia) como su colección de elementos, que debería ser. Si eres un programador, ¿qué vas a hacer?
Responderé tanto al teclado como al mouse.
Una vez que obtengo la entrada del teclado, uso
lcv.MoveCurrentToNext();
o
lcv.MoveCurrentToPrevious();
Entonces, seguro que el teclado funciona bien.
Entonces estoy trabajando en las entradas de Mouse responsables. Y viene el problema.
Quiero escuchar el evento ''MouseClick'' de mi artículo. Pero, probablemente, mi artículo no se genera, es solo un marcador de posición. Entonces, cuando el usuario hace clic en este marcador de posición, no obtengo nada.
Si obtengo el evento con éxito, ¿qué sigue? Voy a invocar
lcv.MoveCurrentTo (selectedItem);
el "elemento seleccionado" que sería nulo no es un parámetro aceptable aquí, creo.
De todos modos, es solo adivinar. No tengo tiempo para depurarlo aunque puedo. Tengo un montón de defectos para arreglar. Buena suerte. :)
Tengo una nueva solución para esta pregunta. "USANDO Mahapps"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
<ComboBox x:Name="bars" **controls:TextBoxHelper.ClearTextButton="True"**
DisplayMemberPath="Name"
Height="21"
SelectedItem="{Binding Bar}"/>
Puede usar el botón de cerrar para borrar el contenido.
Gracias.
Tuve el mismo tipo de problema que hicimos, como agregar una propiedad de valor al elemento de la colección como este:
public class Bar
{
public string Name { get; set; }
public Bar Value
{
get { return String.IsNullOrEmpty(Name) ? null : this; } // you can define here your criteria for being null
}
}
Luego, al agregar elementos en lugar de nulos, uso el mismo objeto:
comboBox1.ItemsSource= new ObservableCollection<Bar>
{
new Bar(),
new Bar { Name = "Hello" },
new Bar { Name = "World" }
};
Y en lugar de selecteditem lo enlace a selectedvalue:
<ComboBox Height="23" Margin="25,40,133,0" DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Bar}"
Name="comboBox1" VerticalAlignment="Top" />
Sé que no es una solución completa, solo una solución que uso
El "elemento" nulo no está siendo seleccionado por el teclado en absoluto, sino que el elemento anterior está siendo deseleccionado y no se puede seleccionar ningún elemento posterior. Esta es la razón por la que, después de "seleccionar" el elemento nulo con el teclado, a partir de ese momento no podrá volver a seleccionar el elemento seleccionado previamente ("Hola"), ¡excepto con el mouse!
En resumen, no puede seleccionar ni anular la selección de un elemento nulo en un ComboBox. Cuando crees que lo estás haciendo, estás deseleccionando o seleccionando el elemento anterior o uno nuevo.
Esto se puede ver mejor si se agrega un fondo a los elementos en el ComboBox. Observará el fondo de color en el ComboBox cuando seleccione "Hola", pero cuando lo deseleccione mediante el teclado, el color de fondo desaparecerá. Sabemos que este no es el elemento nulo, porque el elemento nulo en realidad tiene el color de fondo cuando dejamos caer la lista con el mouse.
El siguiente XAML, modificado a partir de eso en la pregunta original, pondrá un fondo LightBlue detrás de los elementos para que pueda ver este comportamiento.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox x:Name="bars" Height="21" SelectedItem="{Binding Bar}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Background="LightBlue" Width="200" Height="20">
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
Si desea mayor validación, puede manejar el evento SelectionChanged en el ComboBox y ver que "seleccionar el elemento nulo" en realidad da una matriz vacía de AddedItems en SelectionChangedEventArgs, y "deseleccionar el elemento nulo seleccionando ''Hello'' con el mouse" da una matriz vacía de RemovedItems.