WPF ComboBox disabled item still selectable on border - c#

When I disable some combobox items they stays selectable on left and right borders of nested textblock.
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

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>

Related

How do I select a new ListBoxItem in C# WPF after I just inserted it automatically

I have the following problem with my calculator app which I'm doing in the MVVM pattern.
I'm redoing the Windows 10 Calculator in Standard Mode. I made an ObservableCollection of MemoryItem.
MemoryItem is a class that contains an int for the Index, a double for the value and a RelayCommand for the MemoryButtons.
Basically it looks like this and is connected to my ViewModel:
public class MemoryItem
{
public double MemoryItemValue { get; set; }
public int SelectedMemoryItemIndex { get; set; }
public RelayCommand MemoryItemChange { get; set; }
}
So I've binded the SelectedMemoryItemIndex Property to the SelectedItemIndex in WPF.
My ListBox looks like this:
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource MemoryListBoxStyle}"
Visibility="{Binding MemoryVisibility}" ItemsSource="{Binding MemoryCollection}"
SelectedItem="{Binding SelectedMemoryItem}" SelectionMode="Extended" SelectedIndex="{Binding SelectedMemoryItemIndex}"
HorizontalContentAlignment="Right"/>
While the style of it looks like this:
<Style x:Key="MemoryListBoxStyle" TargetType="ListBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<UniformGrid Rows="2" Margin="5">
<TextBlock Style="{StaticResource DisplayStyle}" Text="{Binding MemoryItemValue}" FontSize="20"/>
<DockPanel LastChildFill="False">
<Button Content="MC" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Clear}"/>
<Button Content="M+" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Add}"/>
<Button Content="M-" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Substract}"/>
</DockPanel>
</UniformGrid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The bindings work BUT I don't know how to have the new MemoryItem selected after Inserting the new MemoryItem and deleting the new one. Is there a better of way inserting the new item? ObservableCollection doesn't include a method to update a specific item (as far as I know).
This is the method I'm using to add the value to the MemoryItemValue and insert it in my Collection:
case MemoryUsage.Add:
if (SelectedMemoryItemIndex == -1)
{
SelectedMemoryItemIndex = 0;
}
MemoryItemValue += Eingabe1;
MemoryCollection.Insert(SelectedMemoryItemIndex +1, MItem);
MemoryCollection.RemoveAt(SelectedMemoryItemIndex);
break;
This way it worked but I always have to select the new inserted MemoryItem.
I'm thankful for ANY help provided by you.
Please keep in mind that I'm a beginner in programming and this is my first SO question ever.
Here is a post that helps answer this question.
But basically:
Create an IsSelected property on your MemoryItem class and bind ListBoxItem.IsSelected to that property.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
When you want your new item selected, just set IsSelected to true.
IsSelected = true;
And shazam! It should work.
Here is code copied from another answer that may give you more information.
<ListBox ItemsSource="{Binding Items, Source={StaticResource ViewModel}}"
SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsItemSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemText}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Forgive me for leaving that example exactly as I found it.

DataTrigger on each item's textbox in ItemsControl

I have an ItemsControl that displays a list of Labels & TextBoxes that are used for data input and a button that executes some command when pressed (using the input values):
<DataTemplate x:Key="StringParameterTemplate">
<StackPanel Name="StackPanel_Parameter"
Orientation="Horizontal">
<Label Name="ParameterLabel"
Content="{Binding ParameterLabel}"
HorizontalContentAlignment="Right"
Width="200" />
<TextBox Name="ParameterTextBlock"
Text="{Binding ParameterValue, UpdateSourceTrigger=PropertyChanged}"
Width="300"/>
</StackPanel>
</DataTemplate>
. . .
<!-- Display all parameters -->
<ItemsControl Name="ListView_Parameters"
ItemsSource="{Binding ParameterCollection, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
ItemTemplateSelector="{StaticResource TaskParameterTemplateSelector}"
BorderThickness="0" />
<Button Name="ExecuteTaskButton"
Content="{Binding ButtonLabel}"
Style="{StaticResource ExecuteButtonStyle}"
Command="ExecuteTask">
I would like to create a style that enables/disables the button when ANY of the parameters from ListView_Parameters is empty. Something like this:
<!-- Execute button enable / disable -->
<Style x:Key="ExecuteButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Button.IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ListView_Parameters, Path=ParameterValue}" Value="">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
You can achieve this with a single binding using a converter.
<Button Content="{Binding ButtonLabel}"
IsEnabled="{Binding Path=ItemsSource,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Converter={local:ItemsToBooleanConverter}}" />
Then your converter takes an input of the itemssource (for example a list of objects) - and can return true if all fields you want have values, false otherwise.
The converter is mostly boilerplate, but would look like something this:
public class ItemsToBooleanConverter : MarkupExtension, IValueConverter
... but the important part would like like this, if you were using a list:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var items = value as IList<ParameterList>;
return !(items.Any( <you check here for empty values> );
}
You'll need to be sure your parameter entry fields are bound properly to their sources so that the converter check is current.

WPF Set focus on text box when item added to Listbox

I have a listbox with an Add button mapped to a command backed by a SelectedItem property in the VM.
When an item is added to the listbox i have the SelectedItem set to the new item so it has focus in the listbox. I'd like to have a textbox (data entry for that new item) to have focus.
I've been looking at event triggers but i havent seen a way to cross items but basically I think what i want is an event trigger for the listbox selection change to to set the focus on a text box.
how would i go about doing this?
As an example I have the following XAML code. This will add a Person (name and age property only)
Basically I want the txtName textbox to have focus when an item is selected in the listbox.
<StackPanel>
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding NewPerson}"></TextBox>
<Button Command="{Binding AddPersonDelegateCommand}">Add</Button>
<Button>Remove</Button>
<ListBox ItemsSource="{Binding People}" DisplayMemberPath="Name"
SelectedItem="{Binding SelectedPerson}">
</ListBox>
<TextBox Name="txtName" Text="{Binding SelectedPerson.Name, Mode=TwoWay}"</TextBox>
<TextBox Name="txtAge" Text="{Binding SelectedPerson.Age, Mode=TwoWay}"></TextBox>
</StackPanel>
here is the xaml based trigger to set focus on the txtName TextBox when SelectedItem property is toggled from null
so idea here is that you set SelectedPerson property to null followed by the instance of newly created person object, that should do the trick and will set the focus to the desired TextBox
the limitation of this trigger is that you need to set SelectedPerson property null before you set to the new object, an attached behavior can solve that issue too, if this is not workable for you.
<StackPanel>
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding NewPerson}"></TextBox>
<Button Command="{Binding AddPersonDelegateCommand}">Add</Button>
<Button>Remove</Button>
<ListBox DisplayMemberPath="Name"
x:Name="list"
SelectedItem="{Binding SelectedPerson}">
</ListBox>
<TextBox Name="txtName"
Text="{Binding SelectedPerson.Name, Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem,ElementName=list}"
Value="{x:Null}">
<Setter Property="FocusManager.FocusedElement"
Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="txtAge"
Text="{Binding SelectedPerson.Age, Mode=TwoWay}"></TextBox>
</StackPanel>

How to get first ListBoxItem in the group

I have a ListBox sorted and grouped by SortDescription and GroupDescription and now I want to know whether there is a way to know which item is the first one in each group. For example if I have a list of names I want to get the first Item in the list which starts with A, B, C, etc and change its template.
To be more clear I should say I want to change the DataTemplate of the first ListBoxItem in each group. How is it possible?
Edit
This is my sample CollectionView
<CollectionViewSource
x:Key="Myiew"
Source="{Binding Items}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="FirstName" Converter="{StaticResource StringToFirstLetter}" />
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="FirstName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Listbox ItemsSource={"StaticResources Myiew"} />
As I said everything works fine. the Items are sorted and if I change the GroupStyle I see that the items are grouped, but I don't want to set a group style. I just want to change the DataTemplate of the first item in each group.
Here is a sample for you
create a converter class
this will just see if the value passed is null or not
class FirstItemDetector : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
then comes the declaration of converter where l: is the namespace to your converter class
<l:FirstItemDetector x:Key="FirstItemDetactor" />
then comes the listbox
I added a group style (you can style however you like) solution is group independent
added a data template as ItemTemplate of the listbox
added a DataTrigger on PreviousData with converter as FirstItemDetactor declared as above
in the setter of trigger I changed the foreground to red ( you have many choice including changing styles, show hide elements etc.
here is the listbox
<ListBox ItemsSource="{Binding Source={StaticResource Myiew}}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border BorderBrush="Black"
BorderThickness=".5"
Padding="4">
<TextBlock Text="{Binding Name}"
HorizontalAlignment="Center" />
</Border>
<ItemsPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" x:Name="text" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData},Converter={StaticResource FirstItemDetactor}}"
Value="True">
<Setter TargetName="text"
Property="Foreground"
Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
main role is played by the datatigger and the converter, it will work independently, with groups or even nested groups.

Disallow/Block selection of disabled combobox item in wpf

I'm writing an application wherein I would like to disable few items in the ComboBox and also want to disallow/block selection of disabled items. Please note ComboBox in main window has another ComboBox as ComboBox Item init (that is decided at run time by DataTemplateSelector).
With below code I'm able to disable a ComboBox within ComboBox but it would not stop user from selecting that disabled ComboBox item. Any help in disallow/block selection of disabled items would be helpful.
Below are the code snippets
ComboBox in main window:
<Grid>
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top"
Width="120" Margin="87.2,44.8,0,0"
ItemsSource="{Binding Cars}"
ItemsPanel="{DynamicResource ItemsPanelTemplateHorizontal}"
ItemTemplateSelector="{StaticResource QualityComboBoxTemplateSelector}"
SelectedItem="{Binding SelectedItm}"/>
</Grid>
Data template selector:
public class QualityComboBoxTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var dataTemplate = element.FindResource(((item is string) && item.Equals("Ferrari")) ?
"DataTemplateTopLevelCombobox2" : "DataTemplateTopLevelCombobox1") as DataTemplate;
return dataTemplate;
}
}
Data templates for above ComboBox:
<DataTemplate x:Key="DataTemplateTopLevelCombobox1">
<Border BorderBrush="Black" BorderThickness="1" >
<TextBlock HorizontalAlignment="Left"
TextWrapping="Wrap" Text="{Binding}"
VerticalAlignment="Top"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTopLevelCombobox2">
<Border Width="100">
<ComboBox Text="Custom" Height="21.96"
ItemsSource="{Binding DataContext.Models, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
IsEnabled="{Binding DataContext.EnableCombo, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Border>
</DataTemplate>
You can achieve this by setting IsEnabled property of a ComboBoxItem to false;
So each item in ComboBox's ItemSource (i.e. Cars in your case) can be an object having some property (say IsSelectable) specifying whether it should be enabled or disabled and then use it with a style to make an item un-selectable. something like this -
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsSelectable}"/>
</Style>
Update:
<Grid>
<ComboBox
Width="120"
Margin="87.2,44.8,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplateSelector="{StaticResource QualityComboBoxTemplateSelector}"
ItemsPanel="{DynamicResource ItemsPanelTemplateHorizontal}"
ItemsSource="{Binding Cars}"
SelectedItem="{Binding SelectedItm}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter
Property="IsEnabled"
Value="{Binding IsSelectable}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
To solve the problem pointed by #JordyBoom.
ItemsContainerGenerator does not generate items until dropdown is opened at least once.
So if you open the drop down and close it again in window’s loaded event handler then all supposed to work fine with mouse as well as with keyboard selection.
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(onLoaded);
}
private void onLoaded(object sender, RoutedEventArgs e)
{
cmbx.IsDropDownOpen = true;
cmbx.IsDropDownOpen = false;
}
source: WPF: Making combo box items disabled – also when accessed using the keyboard

Categories