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
Related
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>
I am new in c# and wpf. I have a treeveiew and what I need is - when user click on one of the items, mark this item as selected. For example as it is implemented in DataGrid when user click on the row it marks as selected and user can easaly understand which row was selected.
My implementation of TreeView is
<TreeView Name="Tv_request"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Path=RequestSet}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Childrens}"
DataType="{x:Type local:IRequest}">
<TreeViewItem MouseUp="TreeViewItem_MouseUp"
Header="{Binding TypePage}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Way I can get clicked item - by TreeViewItem_MouseUp event, but I need to implement mark selection item.
How to implement it?
EDIT
In order to get clicked item I am using such approach
private void TreeViewItem_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var tree = sender as TreeViewItem;
if (tree != null)
{
IRequest item = tree.DataContext as IRequest;
Presenter?.TreeViewSelectedItem(item);
}
}
Such way I can have access to my selected item, but if I change it to
private void TreeViewItem_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var tree = sender as ContentPresenter;
if (tree != null)
{
IRequest item = tree.DataContext as IRequest;
Presenter?.TreeViewSelectedItem(item);
}
}
So, tree.DataContext return me NodeType and logically it is ok, because I set
<ContentPresenter Content="{Binding NodeType}"
MouseUp="TreeViewItem_MouseUp"/>
instead of
<TreeViewItem MouseUp="TreeViewItem_MouseUp"
Header="{Binding NodeType}"/>
So, question is, how I can get entire clicked object not just his NodeType?
Firstly, your item template is creating a TreeViewItem inside a TreeViewItem. It should look like this:
<HierarchicalDataTemplate
ItemsSource="{Binding Childrens}"
DataType="{x:Type local:IRequest}">
<ContentPresenter Content="{Binding TypePage}"/>
</HierarchicalDataTemplate>
The TreeViewItem will be automatically created by WPF, and the HierarchicalDataTemplate you provide will be applied to it.
Secondly, TreeViewItem has a property called IsSelected. You can use the Style to bind this property to a property of your IRequest object:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding RequestIsSelected}" />
</Style>
Or you can use an EventSetter to provide a handler for the TreeViewItem.Selected event if you prefer:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<EventSetter RoutedEvent="Selected" Value="TreeViewItem_Selected" />
</Style>
You could also use the TreeView.SelectionChanged event instead of dealing with individual tree view items, but be aware that you (annoyingly!) can't select a new item from the TreeView itself.
If you don't mess things up by nesting a hierarchicaltemplate inside an itemtemplate you don't need to do anything at all to get a selected treeview item coloured.
Look at the xaml here:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.hierarchicaldatatemplate?view=netcore-3.1
<HierarchicalDataTemplate DataType = "{x:Type src:League}"
ItemsSource = "{Binding Path=Divisions}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType = "{x:Type src:Division}"
ItemsSource = "{Binding Path=Teams}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type src:Team}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
Notice what you give it as a template appears inside a treeviewitem. Not hierarchicaldatatemplate within itemtemplate.
Similarly, here: https://www.wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/
<TreeView Name="trvFamilies">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:Family}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/group.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:FamilyMember}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" (" Foreground="Green" />
<TextBlock Text="{Binding Age}" Foreground="Green" />
<TextBlock Text=" years)" Foreground="Green" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
If you try that code in the last link, notice when you click a node - a family or member - you get a blue background.
This is how you mark a selected treeviewitem.
Don't mess up your templates and it "just works".
If you need to know which one was selected then this is a bit tricky because you can't just bind selecteditem like a datagrid. This is indirectly due to the hierarchical nature of the control meaning each node has it's own itemcontrol for children. A treeviewitem is a headereditemscontrol.
This answer illustrates one of the behaviors commonly used to enable binding of selecteditem:
Data binding to SelectedItem in a WPF Treeview
Once you bind the selected item like this you can then work with that property in the viewmodel. When it changes the setter will be hit and you can put code in there to do work with that item.
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.
I have a TextBox inside a ListBoxItem which is disabled so I can drag and drop it in the ListBox.
Once I double click it I want it to be Enabled so I can edit the Text and when I'm done I want it do be Disabled again to do drag and drop.
I have the MouseDoubleClick event on the ListBoxItem but it doesn't change the TextBox ReadOnly. Can anybody tell me how to achieve this.
at the moment TextBox returns null. seems like I don't get access to it the way I'm trying.
XAML
<ListBox Name="Locations" Cursor="Hand" HorizontalAlignment="Left" Height="351" Margin="10,48,0,0" VerticalAlignment="Top" Width="285" ItemsSource="{Binding Locations}" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Name="textBox" Text="{Binding Path=Name}" IsHitTestVisible="False" Width="270" Background="Transparent" BorderThickness="0" Margin="2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In View
private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ListBoxItem item = sender as ListBoxItem;
textBox.IsReadOnly = false;
}
That's because the textbox "doesn't exists" in the visual tree, WPF creates one for each listitem when needed, so you never have a reference to it. It is possible to navigate the visual tree and get the textbox reference but I advise against it, instead, create a property in your item model, something like "EditMode" that when you set it to true, the textbox will enable through the binding of the IsEnabled property.
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>