How to get first ListBoxItem in the group - c#

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.

Related

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.
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>

Variable background color in an itemscontrol

I would like to create an items control that alternates the background color of an item based on the alternation index of the group it belongs to. In reference to the classes I list below, I would like it to, for example, have the background of the first three RandomHouse books as black, and then when it encounters the next Publisher, the background changes back to white, and so on and so forth for as many unique publishers there are. The number and name of the publishers is nondeterministic and is only evaluated at runtime. I have tried to do it with xaml to the best of my ability but it doesnt seem the alternationindex can be accessed for a GroupItem for whatever reason. Any help would be appreciated.
class Book
{
String Publisher {get; set;}
String Title {get; set;}
}
class ViewModel
{
var listBooks = new ObservableCollection<Book>();
listBooks.Add(new Book(){Publisher = "RandomHouse", Title = "Title1"});
listBooks.Add(new Book(){Publisher = "RandomHouse", Title = "Title2"});
listBooks.Add(new Book(){Publisher = "Penguin", Title = "Title5"});
ObservableCollection<Book> ListBookItems {get {return listBooks.Orderby(e => e.Publisher).ToList(); } }
}
<UserControl.Resources>
<Style TargetType="ItemsControl" x:Key="ListBookStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="FontFamily">
<Setter.Value>Consolas</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type models:Book}">
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Publisher" Width="100"/>
<ColumnDefinition SharedSizeGroup="Title" Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Publisher}"
Grid.Column="0"
FontWeight="Bold"
Margin="5"/>
<TextBlock
HorizontalAlignment="Left"
Text="{Binding Title}"
Grid.Column="1"
FontWeight="Bold"
Margin="5"
/>
</Grid>
</DataTemplate>
<CollectionViewSource x:Key="ListBookItems" Source="{Binding ListBookItems}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Publisher"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<DockPanel>
<ItemsControl
ItemsSource="{Binding Source={StaticResource ListBookItems}}"
Style="{StaticResource ListBookStyle}">
<ItemsControl.GroupStyle>
<GroupStyle AlternationCount="2">
<GroupStyle.ContainerStyle >
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FF000000"/>
<!--<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FFD9D9D9"/>
</Trigger>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Foreground" Value="#FF444444"/>
<Setter Property="Background" Value="#FFEFEFEF"/>
</Trigger>
</Style.Triggers>-->
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
You are very close. You just need to specify ItemsControl.AlternationIndex as the property name in each trigger, instead of just AlternationIndex.
That said, frankly I had second-thoughts about even proposing this as an answer, except that I can't actually get that to work. That is, while I can see in the debugger that it correctly sets the GroupItem.Background property value as desired, I see no visible effect on-screen. It's as if the items-presenter for the group is ignoring the GroupItem's background property.
The reason I'm going ahead and posting this as an answer even though it doesn't completely address the issue is that getting the GroupItem.Background property value to be respected by the actual presenter in the GroupItem is a completely different problem than correctly using the AlternationIndex value. So while you can use this answer to get the AlternationIndex value to be correctly bound, you will need to do some more work and/or post another question to dig into why having correctly set the Background property value, that does not actually change the background as shown on the screen.
I wish I could also explain how to get the background property in GroupItem to affect the display on the screen. I hope that if you figure that out, you will at least follow up with a comment here, explaining the answer. :) (Or if you post a question and someone else explains it, comment with a reference to that answer).

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.

Using ValueConverter in Style DataTrigger of ItemsControl but List is Empty

I am attempting to use a ValueConverter to flip the last item in an ItemsControl (so that it appears backwards).
To accomplish this, I created a Style with a DataTrigger which is using a ValueConverter to check if the current item is the last item in the list.
<UserControl.Resources>
<local:FIsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter"/>
</UserControl.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=ActiveAction.ActionIconDatas}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="local:FActionInfoControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsLastItemInContainerConverter}}" Value="True">
<Setter Property="RenderTransformOrigin" Value="0.5 0.5"/>
<Setter Property="RenderTransform">
<Setter.Value>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<ContentControl>
<local:FActionInfoControl/>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
The issue appears to be with my ValueConverter.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DependencyObject item = (DependencyObject)value;
// THIS IS RETURNING NULL
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
if (ic == null)
{
return false;
}
else
{
return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
}
}
Although item is a valid element in the ItemsControl, the call to ItemsControl.ItemsControlFromItemContainer is returning null and I'm not sure why. It is being set, and displays fine (it just is never flipped as the style should cause it to).
Any ideas on this? Thanks!
Try replacing your binding with:
{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}, AncestorLevel=2}, Converter={StaticResource IsLastItemInContainerConverter}}
Actually ItemsControl.ItemsControlFromItemContainer() only returns parent ItemsControl for actual item containers. In your case ContentPresenter is the container type as you use simple ItemsControl. However as you incapsulate your in a ContentControl in your data template it is important to specify AncestorLevel=2 as well.
You are passing local:FActionInfoControl to converter, which is not a container for ItemsControl. You need to pass container i.e. ContentPresenter which you can get using FindAncestor.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ContentPresenter, AncestorLevel=2},
Converter={StaticResource IsLastItemInContainerConverter}}"
Value="True">
.....
</DataTrigger>

How to make a TextBlock appear / disappear based on bound property of another TextBlock?

Supposing that I have two TextBlock elements, one being a label for the second, which is bound:
<TextBlock Margin="0,0,0,0" Text="Notes:" />
<TextBlock Margin="50,0,0,0" Text="{Binding Path=notes}" />
I only want these two TextBoxes to appear if notes!="", that is only if there is something to display. How would one go about this?
Thanks.
so many ways to do it, DataTriggers, doing logic in your ViewModel, DependencyProp's in code behind so you can control everything through binding without any triggers, etc.
or here's a sample doing in XAML only.
Copy/Paste/Run this code:
<Control>
<Control.Style>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<StackPanel x:Name="stackPanel">
<TextBlock Margin="0,0,0,0" Text="Notes:" />
<TextBlock x:Name="txtNotes" Margin="50,0,0,0" Text="{Binding Path=notes}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger SourceName="txtNotes" Property="TextBlock.Text" Value="">
<Setter TargetName="stackPanel" Property="Control.Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Control.Style>
</Control>
First create a converter:
public class EmptyStringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return string.IsNullOrEmpty(value as string)
? Visibility.Collapsed
: Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Then reference it (you can do this in your App resources, in the view resources, etc:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converters="clr-namespace:MyConverterNamespace">
<Converters:EmptyStringToVisibilityConverter
x:Key="EmptyStringToVisibilityConverter"/>
</ResourceDictionary>
Then use it in your controls:
<TextBlock Margin="0,0,0,0" Text="Notes:"
Visibility="{Binding notes,
Converter={StaticResource EmptyStringToVisibilityConverter}"/>
<TextBlock Margin="50,0,0,0" Text="{Binding Path=notes}"
Visibility="{Binding notes,
Converter={StaticResource EmptyStringToVisibilityConverter}"/>

Categories