Hiding A Specific-Indexed Element In An ItemsControl's ItemTemplate - wpf

In my WPF/MVVM app, I've got a ListBox with an ItemTemplate; I'm trying to figure out a way to hide a particular element in the template, only for the 0th item in the list. First, some simplified boilerplate:
<ListBox ItemsSource="{Binding MyItems}" AlternationCount="999999" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType=ListBoxItem}, StringFormat='{}{0}: '}" />
<TextBlock Grid.Column="1" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now let's say I want to hide the "Name" for just the 0th item. As I'd ideally hoped to do this entirely in XAML, I was attempting to leverage an extremely handy extension I often use, CalcBinding, which lets you write calculated binding expressions in XAML (note: it automatically converts between boolean & visibility for you):
<Button Content="Cancel" Command="{Binding CancelCommand}" Visibility="{c:Binding 'ProgressPercent > 0'}"/>
So in this case, I'd thought of something like:
<a:EnumEditBox Visibility="{c:Binding Path='(ItemsControl.AlternationIndex) == 0', RelativeSource={RelativeSource AncestorType=ListBoxItem}}" />
However, this and all other attempts seem to yield errors. In this case, the error is: BindingExpression path error: 'ItemsControl' property not found on 'object' ''ListBoxItem' (Name='')'. BindingExpression:Path=ItemsControl.AlternationIndex; DataItem='ListBoxItem' (Name=''); target element is 'EnumEditBox' (Name=''); target property is 'Visibility' (type 'Visibility')
So my questions are:
Is it possible to do what I'm attempting, solely in XAML?
If not, how might I otherwise accomplish this (to hide an element for the 0-indexed item in a ListBox)?
Any pointers would be greatly appreciated :)

The element in the ItemTemplate could have a Style with a DataTrigger on the AlternationIndex property:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
...
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>

Related

Handling different item templates that share the same content

I have two custom ItemTemplates for the ListBox, one for the regular items, and one for the selected item. An example of how would I handle this is:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource Template2}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
Template1 and Template2 are very similar:
<DataTemplate x:Key="Template1">
<SameContent />
<DifferentContent1 />
</DataTemplate>
<DataTemplate x:Key="Template2">
<SameContent />
<DifferentContent2 />
</DataTemplate>
So, is it a proper way to duplicate the code for the SameContent (which is like a bunch of TextBlocks, Panels, etc) in both templates, or it is a better approach to have only one template, but switch the DifferentContent based on the IsSelected property, or...?
if second approach, how would it be properly done?
Obviously duplicating the code is not a very good solution. A better approach is to define another DataTemplate as your common content and then use ContentPresenter to present it:
<Window.Resources>
<DataTemplate x:Key="CommonTemplate">
<TextBlock Text="{Binding CommonProperty1}" />
<TextBlock Text="{Binding CommonProperty2}" />
</DataTemplate>
<DataTemplate x:Key="Template1" >
<StackPanel>
<ContentPresenter ContentTemplate="{StaticResource CommonTemplate}"/>
<TextBlock Text="{Binding Template1Property1}"/>
<TextBlock Text="{Binding Template1Property2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Template2" >
<StackPanel>
<ContentPresenter ContentTemplate="{StaticResource CommonTemplate}"/>
<TextBlock Text="{Binding Template2Property1}"/>
<TextBlock Text="{Binding Template2Property2}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>

WPF ListBox Item collapse datatrigger not working

I have a listbox and all I want to do is collapse the listboxitem based on a boolean property of my SelectedItem.
The IsVisible property on my client Model implements the NotifyPropertyChanged event.
Overview - I have a list of clients which users can do CRUDs on. When they delete, I set a boolean property on the Model which my VM exposes to the View. This should then only hide the 'deleted' row from the list. During a flush to db I CRUD based on the mode of the model.
<ListBox Name="listClients"
Grid.Column="1" Grid.Row="1"
Margin="0" BorderThickness="0"
Height="auto"
Style="{StaticResource BumListBox}"
SelectionMode="Extended"
ItemsSource="{Binding Path=ClientList}"
SelectedItem="{Binding SelectedClient, Mode=TwoWay}"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ClientNo}" Foreground="White" FontSize="{StaticResource HeaderFontSize}" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Text="{Binding ClientDesc}" Foreground="White" FontSize="{StaticResource SubHeaderFontSize}" FontWeight="Light" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to jippo MVVM process:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (_cvm.SelectedClient != null)
{
_cvm.SelectedClient.IsVisible = !_cvm.SelectedClient.IsVisible;
_cvm.CurrentSelectedIsVisible = _cvm.SelectedClient.IsVisible; //<- another option to bind to
}
}
I've tried the these suggestions here and here or something similar but I just can't get to hide the items.
Any help in the right direction would be great, cheers.
Edit
I've tried Blam's suggestion below like this but still unable to hide the items:
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding Path=CurrentIsVisible, Converter={StaticResource b2v}}" />
</Style>
You will need to set up a converter if you are returning true/false but there is a system converter for that
Move it up to Resources
I have know I have used it this way
<ListBox x:Name="lb" ItemsSource="{Binding}" DisplayMemberPath="Text">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding Path=Vis}" />
</Style>
</ListBox.Resources>
</ListBox>
This was rather frustrating and the solution so simple. My client model with IsVisible is in a dll and the NotifyPropertyChanged() changes were never built to update the reference in my project..so the binding never happened. These late nights are taking their toll.

Something Like For Loop in XAML

I have a Resource Dictionary in which I want to have a common DataTemplate for ComboBox.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate DataType="{x:Type ComboBox}">
<StackPanel Orientation="Horizontal">
<!--Here I need to use something like For Loop-->
<TextBlock Text=""></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
Now I have created a dependency property of type integer named NoOfColumns. While declaring the comboBox I need to set the NoOfColumns property to automatically generate that number of columns. I want them to databind.
Update as requested by Joe
<ComboBox x:Name="cbUnder" ItemsSource="{Binding GroupsAndCorrespondingEffects}"
IsEditable="True" SelectedItem="{Binding SelectedGroup, Mode=TwoWay}"
Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}" TextSearch.TextPath="GroupName"
Grid.Column="1" Grid.ColumnSpan="4" Grid.Row="3">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type vm:GroupAndCorrespondingEffect}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" Width="250">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding CorrespondingEffect}" />
</StackPanel>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
There's nothing like for in XAML, but ItemsControl is very much like foreach. Instead of setting an int property, make an ObservableCollection<T> and add that many objects to it, and then bind the ItemsControl to your collection property.
This has the added benefit that each collection item can expose properties to be bound, e.g. if you wanted to display different text in each TextBlock, you could put a property on your collection item and bind the TextBlock to that property.

ComboBox with two columns in popup

I need to create a custom control containing a combobox whose popup will have the Name propeprty of the bound objects aligned to the left, and the CreatedDate property of the bound objects aligned to the right in each pop up item. Also the Name and the CreatedDate must not overlap. The Name of the object is of variable length
I tried solving this problem using a DataTemplate in the Combobox.ItemTemplate, inside the data template I have a grid with two columns aligned appropriately. The Grid's horizontal alignment is set to Stretch but for some reason the Grid doesn't fill out the available space in the popup. Does anyone know how to get around this and why it happens? I am using WPF 3.5.
<UserControl.Resources>
<CollectionViewSource x:Key="cvsProcessingSessionList" Source="{Binding ProcessingSessionList, ElementName=View}"/>
<Style TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource {x:Static res:ObjectResources.LEComboBoxStyle}}">
<Setter Property="Margin" Value="5,3"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<GroupBox x:Name="grbProcessingSession">
<GroupBox.Header>
<Bold>Processing Session:</Bold>
</GroupBox.Header>
<GroupBox.Content>
<ComboBox
x:Name="ccbProcessingSessionName"
ItemsSource="{Binding Source={StaticResource cvsProcessingSessionList}}"
Loaded="OnLoaded" TextSearch.TextPath="Name"
IsEditable="True" MaxDropDownHeight="500" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Background="Pink" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Left" HorizontalAlignment="Left"
Margin="0,0,5,0" Text="{Binding Name}"/>
<TextBlock Grid.Column="1" TextAlignment="Right" HorizontalAlignment="Right"
Text="{Binding CreatedDate, StringFormat=dd/MM/yyyy}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Content>
</GroupBox>
Just add this binding to your grid in yiour DataTemplate ...
Width="{Binding ActualWidth,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ComboBoxItem}},
Mode=OneTime}"
Also for better effect, apply the background color to the ComboBoxItem and not to the grid...
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Background" Value="Pink"/>
</Style>
</ComboBox.ItemContainerStyle>
Whenever I want to do the same that you are asking I add:
<ComboBox ...>
<ComboBoxItem HorizontalContentAlignment="Stretch">
</ComboBoxItem>
</ComboBox>
The thing is that I place my custom DataTemplate inside the ComboBoxItem.
Maybe you should try it...
Actually, the better way is to set HorizontalContentAlignment="Stretch" at the ComboBox level. This will work even if width of ComboBox changes (e.g. if it is in resizable container).
However, be aware that this works in WPF and Silverlight 5 but don't work in some older versions of Silverlight.

Binding a checkbox in a datagrid header

I have a datagrid where the first column contains a checkbox to let the user selects specific rows. I have added a checkbox in the datagrid column header to check or uncheck all the rows.
Is it possible to add this functionality only with binding in XAML (no checked event).
<sdk:DataGrid AutoGenerateColumns="False" Grid.Row="1" Name="grid" ItemsSource="{Binding myCollection, Mode=TwoWay}" >
<sdk:DataGrid.Columns>
<sdk:DataGridCheckBoxColumn Binding="{Binding myCollection.UserSelected, Mode=TwoWay}" >
<sdk:DataGridCheckBoxColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox x:Name="checkAll" IsThreeState="True"
IsChecked="{Binding myCollection.UserSelected, Mode=TwoWay, Converter={StaticResource threeStateConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridCheckBoxColumn.HeaderStyle>
</sdk:DataGridCheckBoxColumn>
<sdk:DataGridTextColumn Binding="{Binding Description}" Header="Chemin" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
I think there's something wrong with the "myCollection.UserSelected" part. ThreeStateConverter is a value converter that would return null when some items are selected, true when they are all selected, etc. but the Convert method is never called (even though the PropertyChanged event is raised when UserSelected is changed).
Any idea on how I can do it? Thank you.
Probably, you've solved the problem already, but nevertheless:
<navigation:Page.Resources>
<model:MyModel x:Key="Model"/>
</navigation:Page.Resources>
...
<data:DataGridTemplateColumn Width="Auto" >
<data:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="datap:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsThreeState="True" Margin="2,0,-13,0" DataContext="{StaticResource Model}" IsChecked="{Binding Path=AllChecked, Mode=TwoWay}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</data:DataGridTemplateColumn.HeaderStyle>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
MyModel class contains bool? property that implements logic of selection. It is also used as a `DataContext of page.
I must admit that this potentially has problems if we need to change model.
EDIT: I found another way:
<navigation:Page.DataContext>
<model:MyModel />
</navigation:Page.DataContext>
...
<data:DataGridTemplateColumn Width="Auto" >
<data:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="datap:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsThreeState="True" Margin="2,0,-13,0" IsChecked="{Binding Path=DataContext.AllChecked, ElementName=LayoutRoot, Mode=TwoWay}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</data:DataGridTemplateColumn.HeaderStyle>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
Here we bind to DataContext of root layout element (usually called LayoutRoot) which inherits its data context from page by default.

Resources