Search code examples
c#wpfcombobox

WPF ComboBox disabled item still selectable on border


When I disable some combobox items they stays selectable on left and right borders of nested textblock.

combobox screenshot

I've tried to set margins of textbox and padding of combobox items to 0, then I've tried set HorizontalAlignment property of textbox and combobox item to "Stretch", with no result.

WPF:

<Window.Resources>
    <local:ComboboxItemsDisableConverter x:Key="ComboboxItemsDisableConverter"/>
</Window.Resources>

<ComboBox x:Name="comboBox" HorizontalAlignment="Right" Margin="0,13,10,0" Width="441"
SelectedIndex="{Binding ViewModel.SelectedNic, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" 
ItemsSource="{Binding ViewModel.NICs, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" 
SelectionChanged="ComboBox_SelectionChanged" 
IsReadOnly="True" Height="25" VerticalAlignment="Top" Grid.Row="2">
    <ComboBox.ItemContainerStyle>
        <Style TargetType="ComboBoxItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        </Style>
    </ComboBox.ItemContainerStyle>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <ComboBoxItem IsEnabled="{Binding OperationalStatus, Converter={StaticResource ComboboxItemsDisableConverter}}" >
                <TextBlock HorizontalAlignment="Stretch" Text="{Binding Description}" />
            </ComboBoxItem>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

ComboBoxItemsDisableConverter Class:

class ComboboxItemsDisableConverter : IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        if (value == null) return value;
        var Status = (OperationalStatus)value;

        if (Status != OperationalStatus.Up)
            return true;
        else
            return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new NotImplementedException();
    }
}

What can I do to prevent selection of disabled items completely?

Hiding items works with this code:

<ComboBox.ItemContainerStyle>
            <Style TargetType="ComboBoxItem">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=OperationalStatus, Converter={StaticResource ComboboxItemsDisableConverter}}" Value="true">
                        <Setter Property="Visibility" Value="Collapsed"></Setter>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ComboBox.ItemContainerStyle>

If I use this markup

<ComboBox x:Name="comboBox" HorizontalAlignment="Right" Margin="0,13,10,0" Width="441" SelectedIndex="{Binding SelectedNic}"  ItemsSource="{Binding NICs}" SelectionChanged="ComboBox_SelectionChanged" IsReadOnly="True" Height="25" VerticalAlignment="Top" Grid.Row="1">
        
        <ComboBox.ItemTemplate>
            <DataTemplate>                    
                    <TextBlock HorizontalAlignment="Stretch" Text="{Binding Description}" IsEnabled="{Binding OperationalStatus, Converter={StaticResource ComboboxItemsDisableConverter}}"></TextBlock>                    
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>

no items disabled


Solution

  • You were trying to disable the item containers content, instead of the item container.

    You must understand that ItemsControl contains items in its ItemsSource. Usually this items are data models. Those models are than wrapped into a container. Data models usually are not of type FrameworkElement, they are plain data types. In order to render elements they must be of type FrameworkElement, that's why the models are wrapped into a container e.g. ComboBoxItem. You can layout the content of this container by defining an ItemTemplate.

    You don't interact with the data model (the container content), but with the item container. When you only disable the content you still can interact with the container. The item itself has Padding applied. Therefore there is still enough area to allow interaction.

    To solve your problem you must disable the container. To do so you have define a trigger or setter in the ItemContainerStyle. Note that the DataContext of this Style is the data model (the items inside the ItemsSource):

    <ComboBox>
      <ComboBox.ItemContainerStyle>
        <Style TargetType="ComboBoxItem">
          <Setter Property="IsEnabled" 
                  Value="{Binding OperationalStatus, Converter={StaticResource ComboboxItemsDisableConverter}}" />
        </Style>
      </ComboBox.ItemContainerStyle>
    
      <!-- Remove the IsEnabled binding! -->
      <ComboBox.ItemTemplate>
        <DataTemplate>                    
          <TextBlock HorizontalAlignment="Stretch" 
                     Text="{Binding Description}" />
        </DataTemplate>
      </ComboBox.ItemTemplate>
    </ComboBox>
    

    Note that from a user perspective it is recommended to remove disabled items from the source collection using filtering instead. Don't show content that takes away space and doesn't allow interaction. It can be quite confusing, especially if the user doesn't understand the reason why the items are disabled and how he can get them enabled in order to select them.

    ViewModel.cs

    class ViewModel
    {
      public ObservableCollection<MyModel> NICs { get; }
    
      public ViewModel()
      {
        this.NICs = new ObservableCollection<MyModel>();
    
        // Only show items where OperationalStatus == OperationalStatus.Up
        CollectionViewSource.GetDefaultView(this.NICs).Filter = 
          item => (item as MyModel).OperationalStatus == OperationalStatus.Up;
      }
    }
    

    MainWindow.xaml

    <Window>
      <Window.DataContext>
        <ViewModel  />
      </Window.DataContext>
    
      <ComboBox ItemsSource="{Binding NICs}" />
    </Window>