Bind WPF Expander.IsExpanded property to ListBox.ItemCount with IValueConverter - c#

I have a ListBox that shows search results, grouped by their type, implemented like this:
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<TextBlock Margin="10 5 0 0">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="Name" />
<Binding Path="ItemCount" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
This is working fine.
Now I want to collapse the Expander if there are 10 or more items in a group (only after count changes, keeping the possibility for the user to expand and collapse manually). How can I achieve this?
I wrote an IsLessThanConverter : IValueConverter that takes a parameter and returns true if the passed value is less than the parameter.
However, I don't know how to integrate this converter here. I tried something like this, but the converter is never called:
<Expander IsExpanded="{Binding Path=Count,
RelativeSource={RelativeSource Self},
Converter={StaticResource IsLessThanConverter},
ConverterParameter=10,
Mode=OneWay}">
...

Related

ListBox Grouping WPF

I'm trying to group a listbox. And all I can get to show up is the header.
I've got a list list of 'Online Users' , which look like this.
public class OnlineUser{
public string Branch {get;set;}
public string FirstName{get;set;}
public string LastName{get;set;}
}
Then i populate the list with some users, and put that list into a ICollectionView 'FilterableOnlineUsers'
FilterableOnlineUsers = CollectionViewSource.GetDefaultView(OnlineUsers);
FilterableOnlineUsers.GroupDescriptions.Add(new PropertyGroupDescription("Branch"));
FilterableOnlineUsers.SortDescriptions.Add(new SortDescription("Branch", ListSortDirection.Descending));
And in my Xaml:
<ListBox SelectedItem="{Binding DataContext.SelectedUser" ItemsSource="{Binding DataContext.FilterableOnlineUsers" >
<ListBox.GroupStyle>
<GroupStyle />
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName"></Binding>
<Binding Path="LastName"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
All I can get to show up in the Listbox is the branch name. I can't get first name or lastname to show up underneath the group Descriptions..
Thanks.
You should define a CollectionViewSource in XAML resource Like Below and make the ItemsSource set to the CollectionViewSource,
<CollectionViewSource x:Key="ListBoxItems" Source="{Binding Path=ListOfOnlineUser}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Branch" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
In List Box
<ListBox ItemsSource="{Binding Source={StaticResource ListBoxItems}}"/>
Detail
Below is the ListBox show grouped branch and each branch is inside an expander which you can collapse and expand each groups.
<ListBox
Margin="0,0,5,0"
ItemsSource="{Binding Source={StaticResource ListBoxItems}}"
SelectedIndex="-1"
SelectedItem="{Binding SelectedBranch}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander
Padding="0"
BorderThickness="0"
Header="{Binding Name}"
IsExpanded="True">
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

WPF Changing ListBox background color, enabled and disabled

I'm searching how to change the background color of my WPF Listbox, nothing worked... I don't want to change the background of the Item Template, but of the ListBox himself.
I tried the differents solutions answered here Answers
Here is my Listbox :
<ListBox Name="myListBox" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="myListBox_SelectionChanged" Background="#FFC3DDF7" Margin="0,0,0,10">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Value.IsOk}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Width="200">
<TextBlock FontSize="10" FontWeight="Bold" VerticalAlignment="Center" Text="{Binding Path=Key}" />
<TextBlock FontSize="10" VerticalAlignment="Center" TextWrapping="Wrap">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} {2}">
<Binding Path="Value.TextA" />
<Binding Path="Value.TextB" />
<Binding Path="Value.TextC" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Maybe the item template is on the foreground ?
Tried these codes :
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Blue"/>
</Style.Resources>
</Style>
and
<ListBox>
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
Not working. The down right corner, between the two scrollbars, became blue... But that's all !
The reason why your linked answer does not work is because you have the background of the listbox set to #FFC3DDF7 on the element itself. If you wish to change the initial background color of your listbox this would need to be moved into the style. If you do not move this then the rule for "closest" defined value would be the one on the element itself and the style can't override this.
<ListBox Name="myListBox" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="myListBox_SelectionChanged" Margin="0,0,0,10">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="#FFC3DDF7"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="LightGray" />
<Setter Property="Background" Value="LightGray" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Value.IsOk}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Width="200">
<TextBlock FontSize="10" FontWeight="Bold" VerticalAlignment="Center" Text="{Binding Path=Key}" />
<TextBlock FontSize="10" VerticalAlignment="Center" TextWrapping="Wrap">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} {2}">
<Binding Path="Value.TextA" />
<Binding Path="Value.TextB" />
<Binding Path="Value.TextC" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As Anthony pointed out, your style trigger will never set the Background of your ListBox because you have set a local value. It's called "Dependency Property Precedence". Take a look at this link: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-property-value-precedence.
"Local" values have higher precedence over "style trigger" values. However, "style trigger" values have higher precedence over "style setter" values. This means that if you want to have an initial value for the property and change it based on a trigger, just set it in the style initially, instead of setting it locally like your code does.
First off all - this msdn link ListBox Styles and Templates will help you the most. You can check this way - what applies where in a ListBox and a ListBoxItem.
If you check the Style of the ListBoxItem you will see that the ContentPresenter gets no Foreground applied.
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"/><!-- No foreground gets applied. -->
<Rectangle x:Name="FocusVisualElement" Stroke="#FF6DBDD1" StrokeThickness="1" Visibility="Collapsed" RadiusX="1" RadiusY="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Anyway here is your solution:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Width="200">
<TextBlock VerticalAlignment="Center"
FontSize="10"
TextWrapping="Wrap">
<Run FontWeight="Bold" Text="{Binding Path=Key}" />
<LineBreak />
<Run>
<Run.Text>
<MultiBinding StringFormat="{}{0} {1} {2}">
<Binding Path="Value.TextA" />
<Binding Path="Value.TextB" />
<Binding Path="Value.TextC" />
</MultiBinding>
</Run.Text>
</Run>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsOk}" Value="True" >
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The TextBlock now listens to isOk and will applys the Foreground.

listview conditional grouping

In my WPF app I have a ListView of documents with grouping of sections:
myitems.Add(new Data("document_1", "section_1"));
myitems.Add(new Data("document_1", "section_2"));
myitems.Add(new Data("document_2", "one_and_only_section"));
lv.ItemsSource = myitems;
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lv.ItemsSource);
view.GroupDescriptions.Clear();
view.GroupDescriptions.Add(new PropertyGroupDescription("document");
This results in something that roughly looks like
< document_1
section_1
section_2
< document_2
one_and_only_section
This is in theory fine, but it is very tedious to select the "one_and_only_section" item if everything is collapsed, because it needs two clicks (first on "document_2", second on "one_and_only_section"). Ideally, the document_2 shouldn't be grouped in the same way as document_1:
< document_1
section_1
section_2
document_2
So if there is just one element to a group, it shouldn't have an expander and reveal that one element. If selected it should act as if "one_and_only_section" is selected.
Is this feasible with ListView?
I was able to produce the desired output with the following XAML code:
<ListView ItemsSource="{Binding Path=ItemsView}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<ControlTemplate.Resources>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<TextBlock Text="{Binding Path=Section}" />
</DataTemplate>
</ControlTemplate.Resources>
<Expander Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}">
<Expander.Header>
<StackPanel Margin="0,8,0,0"
HorizontalAlignment="Stretch"
Orientation="Horizontal">
<TextBlock x:Name="Title"
VerticalAlignment="Center"
FontWeight="Bold">
<Run Text="{Binding Path=Name, Mode=OneWay}" />
<Run Text=" " />
<Run Text="{Binding Path=Items.Count, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<ControlTemplate.Resources>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<TextBlock Text="{Binding Path=Document}" />
</DataTemplate>
</ControlTemplate.Resources>
<ItemsPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
You might want to add some extra attention to your expander style and the datatemplates, to make it look similar.

How to set TabIndex to a ComboBox within a ContentControl?

I have following code -
<ContentControl KeyboardNavigation.TabIndex="6"> //Point A
<ContentControl.Template>
<ControlTemplate>
<ContentControl x:Name="content">
<ContentControl.Template>
<ControlTemplate>
<ComboBox Style="{StaticResource ComboBoxStyle}" ItemsSource="{Binding Path=Property1}"
Margin="0, 7" >
<ComboBox.SelectedValue>
<Binding Path="PropertyText" />
</ComboBox.SelectedValue>
</ComboBox>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Property1}" Value="val1">
<Setter TargetName="content" Property="Template">
<Setter.Value>
<ControlTemplate>
<ComboBox Style="{StaticResource ComboBoxStyle}" ItemsSource="{Binding Path=Propert2}"
Margin="0, 7">
<ComboBox.SelectedValue>
<Binding Path="val2" />
</ComboBox.SelectedValue>
</ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
Explanation
See the Point A. The TabIndex is working fine, but if I move this to internal elements like ComboBox, it goes to next TabIndex. Also I have a trigger.
Requirement
Whichever Combobox is visible should get TabIndex="6".
The parent control i.e. ContentControl should not get any TabIndex.
Please suggest. Thanx in advance.

bind the date of the DatePicker to ConverterParameter

I'm trying to mark Names in a ComboBox based on a Date from a DatePicker using a converter class.
My current problem is I don't know how to bind the date of the DatePicker to the "ConverterParameter". Any suggestions?
(probably more errors in my code but i'm stuck at this point)
<Page.Resources>
<Style TargetType="ComboBoxItem" x:Key="combostyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<ControlTemplate.Resources>
<src:ColorFromMagazijnierIdConverter x:Key="conv" />
</ControlTemplate.Resources>
<Grid ToolTip="{Binding Converter={StaticResource conv}, ConverterParameter={ BIND THIS TO THE DATEPICKER DATE }, Mode=OneWay}">
<Rectangle x:Name="MarkedItemBackground" IsHitTestVisible="False" Fill="#80FF0000" />
<!--...-->
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource conv}}"
Value="{x:Null}">
<Setter TargetName="MarkedItemBackground"
Property="Visibility" Value="Hidden" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid Margin="10,10,10,0" Name="rootGrid">
<ComboBox Name="collectMagazijnierComboBox"
DisplayMemberPath="User.Name"
ItemContainerStyle="{DynamicResource ResourceKey=combostyle}"/>
<DatePicker Name="collectDatePicker" />
</Grid>
The ConverterParameter property cannot be the target of a binding. Only a DependencyProperty of a DependencyObject can be the target of a binding.
You'll need to use a MultiBinding:
<Grid>
<Grid.ToolTip>
<MultiBinding Converter="{StaticResource conv}" Mode="OneWay">
<Binding /> <!-- this mimics your current binding to the datacontext itself -->
<Binding ElementName="collectDatePicker" Path="SelectedDate" />
</MultiBinding>
</Grid.ToolTip>
<Rectangle x:Name="MarkedItemBackground" IsHitTestVisible="False" Fill="#80FF0000" />
<!--...-->
</Grid>
You'll then need to rewrite your ColorFromMagazijnierIdConverter converter to implement the IMultiValueConverter interface instead, in which you can access both values.
Although, I'm not 100% sure whether you can reference the collectDatePicker by ElementName from within the style resource like that. But sure you can play around with it!

Categories