Show tick image when item is selected in WPF Listbox - wpf

I have this simple ListBox which displays a list of images horizontal en vertical.
I have also added a tick image on every image. Now I would like to enable this tick image only when the item is selected in the Listbox.
How can I achieve this?
ListBox XAML:
<ListBox x:Name="PhotoCollection"
Grid.Row="2"
Grid.ColumnSpan="4"
ItemsSource="{Binding PhotoCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border BorderBrush="White"
BorderThickness="2"
Margin="5"
Background="LightGray">
<Grid>
<Image Source="{Binding}"
Stretch="Uniform"
Width="50"
Height="50"
Margin="5" />
<Image Source="{StaticResource Check_24}"
Visibility="{Binding Converter={StaticResource VisibleConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1},Path=IsSelected}"
Stretch="Uniform"
Width="20"
Height="20"
Margin="5"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"/>
</Grid>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
EDIT: This line does the trick
Visibility="{Binding Converter={StaticResource VisibleConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1},Path=IsSelected}"

It should work when you bind the IsSelected property of the ListBoxItem to your property IsVisible. But it depends on how you have implemented your ViewModel and the properties.
<ListBox>
<!-- the rest of the XAML-Definition of your ListBox -->
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsVisible, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

When I need to bind visibility of items to boolean values, I've been using a converter class:
public class BoolToVisibleOrHidden : IValueConverter {
public BoolToVisibleOrHidden() { }
public bool Collapse { get; set; }
public bool Reverse { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
bool bValue = (bool)value;
if (bValue != Reverse) {
return Visibility.Visible;
} else {
if (Collapse)
return Visibility.Collapsed;
else
return Visibility.Hidden;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible)
return !Reverse;
else
return Reverse;
}
}
After grabbing an instance of it in XAML like so :
<local:BoolToVisibleOrHidden x:Key="BoolToVisConverter" Collapse="True" />
I can bind visibility of items like this:
Visibility="{Binding Converter={StaticResource BoolToVisConverter}, Path=DataContext.PATHTOBOOLEAN}"

Related

WPF Combobox always displays icon of the first element

I have a combobox in a custom window where I display a language short name its corresponding icon
<ComboBox SelectionChanged="LanguagesListBoxSelectionChanged" WindowChrome.IsHitTestVisibleInChrome="True"
Visibility="{TemplateBinding IsLanguageSwitchComboBoxVisible}"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged, Path=Languages}"
IsEnabled="True" DockPanel.Dock="Right" Width="90" Height="28"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= SelectedLanguage}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<TextBlock FontSize="24" VerticalAlignment="Center" Text="{Binding LanguageName}" Margin="0 0 4 0"></TextBlock>
<Image Height="24" Width="24">
<Image.Source>
<BitmapImage UriSource="{Binding LanguagePicture, Converter={StaticResource DebugDummyConverter}}" />
</Image.Source>
</Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Custom window:
public class CustomTitleBarWindow : Window
{
public CustomTitleBarWindow()
{
Languages = new List<LanguageModel>()
{
new() {Id="ru", LanguageName = "RU", LanguagePicture = "../../Images/ru.ico" },
new() {Id="en", LanguageName = "EN", LanguagePicture = "../../Images/en.ico" }
};
SelectedLanguage = Languages.First();
}
public static readonly DependencyProperty SelectedLanguageProperty =
DependencyProperty.Register("SelectedLanguage", typeof(LanguageModel), typeof(CustomTitleBarWindow), new PropertyMetadata(null));
public LanguageModel SelectedLanguage
{
get => (LanguageModel)GetValue(SelectedLanguageProperty);
set => SetValue(SelectedLanguageProperty, value);
}
...
Item list displays correctly.
The issue is when I select any item, the combobox displays a proper text ("EN" or "RU") but the icon displayed is the one at "../../Images/ru.ico" path.
If I put "../../Images/en.ico" into the list first then the "english" icon will be displayed no matter what item was selected.
Solved:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<TextBlock FontSize="24" VerticalAlignment="Center" Text="{Binding LanguageName}" Margin="0 0 4 0"></TextBlock>
<Image Height="24" Width="24" Source="{Binding LanguagePicture, Converter={StaticResource ImageConverter}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
And an ImageConverter
public class ImageConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The problem is that BitmapImage needs a value immediately whereas binding provides it later.

Text separator between ListView in WPF

I'm trying to get the following from a ListView:
Text | Text | Text
I've already achieved the vertical orientation by the following
<ItemsPanelTemplate><StackPanel Orientation="Horizontal"/></ItemsPanelTemplate>
Each part(Text) is a TextBlock bound to a string in MVVM.
Preferably the lines between should just be regular vertical bars.
Any tips for achieving these vertical bars as specified??
Duplicate of How can a separator be added between items in an ItemsControl, try this:
<ItemsControl Name="theListBox">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="seperator" Text=" | "/>
<TextBlock Text="{Binding}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter Property="Visibility" TargetName="seperator" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In case you have achieved to show separator in between two items, you can have a converter say LastItemInContainerToVisibilityConverter which will be binded to the visibility of separator making separator collapsed for last item and visible for all other items.
Assuming you have used Rectangle to show the seperation between items -
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<Rectangle Visibility="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ListViewItem},
Converter={StaticResource
LastItemInContainerToVisibilityConverter}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
Here goes your converter which will return if it's last item in collection -
public class LastItemInContainerToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
DependencyObject item = (DependencyObject)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
return (ic.ItemContainerGenerator.IndexFromContainer(item)
== ic.Items.Count - 1) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}

WPF nullreference exception thrown on Button when value converter in prior Label is in place

TLDR:
A Nullreference exception is thrown when XAML parsing any button element when another unrelated element has databinding and value converter. When the buttons are commented out, or the databinding is removed the form works.
I have a WPF UserControl with a list box in it which has a DataTemplate with multiple controls in it. I also have a bool to visibility value converter which I use in different locations in the control. I added a new static reference of the converter to the control (different bool to visibility values) and bind it to a label and suddenly the app crashes upon loading the control.
I remove the binding and all is well again. The converter is not at fault though; I put break points in its constructor as well as the convert methods, and it never reaches it. The exception is in the parsing of the XAML, not at the label, but at the first button declared, which is 100% unrelated to the label. If I remove the value converter binding from the label, the XAML parses correctly and the button has no issues.
However, to complicate things, if I comment out that button and every other button in the XAML, it also parses correctly and the value converter works without a problem.
What am I missing?
XAML:
<UserControl x:Class="Customer_Management.OpportunityControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:l="clr-namespace:Customer_Management"
d:DesignHeight="300" d:DesignWidth="300" MaxHeight="200" BorderBrush="DarkGray" BorderThickness="1" x:Name="ucOpp">
<UserControl.Resources>
<l:NullToVisibilityConverter NullValue="Hidden" NonNullValue="Visible" x:Key="NullToHidden"></l:NullToVisibilityConverter>
<l:BoolToVisibilityConverter TrueValue="Visible" FalseValue="Hidden" x:Key="TrueToVisible"></l:BoolToVisibilityConverter>
<l:BoolToVisibilityConverter TrueValue="Hidden" FalseValue="Visible" x:Key="FalseToVisible"></l:BoolToVisibilityConverter>
</UserControl.Resources>
<ScrollViewer>
<ListBox Name="lbxOpps" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" BorderThickness="1">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Path=Opportunity.Name}" Margin="0,1,3,1"></TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Opportunity.Amount, StringFormat=\{0:C\}}" Margin="0,1,3,1"></TextBlock>
<Button Name="btnFinishOrder" Click="btnFinishOrder_Click">Finish Order</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,1,3,1">Invoice #</TextBlock>
<TextBox Name="tbxInvoiceNumber" Text="{Binding Path=InvoiceNumber}"></TextBox>
</StackPanel>
<ListBox ItemsSource="{Binding Path=Batches}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label FontWeight="Bold" Visibility="{Binding Path=IsOcc, Converter={StaticResource TrueToVisible}}">OCC #:</Label>
<ComboBox Margin="0,0,0,0" Name="cboLicense" SelectedValue="{Binding Path=SLicense}" DisplayMemberPath="LicenseID"
SelectedValuePath="LicenseID" ItemsSource="{Binding ElementName=ucOpp, Path=Licenses}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cboLicense}" Click="Button_ClearContorl">X</Button>-->
<Label Margin="0,0,3,0" >R #:</Label>
<!--<Button ToolTip="Click to change" Name="btnLicFile" Click="Button_Click" >LIC File</Button>-->
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=btnLicFile}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
<Label Margin="0,0,3,0" >P #:</Label>
<ComboBox Margin="0,0,0,0" Name="cbxPNum" SelectedValue="{Binding Path=PNum}" DisplayMemberPath="Name"
SelectedValuePath="Id" ItemsSource="{Binding ElementName=ucOpp, Path=Nums}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cbxPNum}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
<Label Margin="0,0,3,0" >U #:</Label>
<ComboBox Margin="0,0,0,0" Name="cbxUNum" SelectedValue="{Binding Path=UNum}" DisplayMemberPath="Name"
SelectedValuePath="Id" ItemsSource="{Binding ElementName=ucOpp, Path=Nums}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cbxUNum}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<Label HorizontalAlignment="Stretch" Background="LawnGreen" FontSize="24" Opacity="0.8" VerticalAlignment="Stretch" Visibility="{Binding Path=IsProcessing, Converter={StaticResource TrueToVisible}}"></Label>
<Label HorizontalAlignment="Center" FontSize="24" VerticalAlignment="Center" Visibility="{Binding Path=IsProcessing, Converter={StaticResource TrueToVisible}}">Processing...</Label>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
ValueConverter:
public sealed class BoolToVisibilityConverter : IValueConverter
{
public Visibility TrueValue { get; set; }
public Visibility FalseValue { get; set; }
public BoolToVisibilityConverter()
{
// set defaults
TrueValue = Visibility.Visible;
FalseValue = Visibility.Collapsed;
}
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (!(value is bool))
return null;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (Equals(value, TrueValue))
return true;
if (Equals(value, FalseValue))
return false;
return null;
}
}

Datagrid expander (from grouping) header

Following this tutorial I had an idea to put in the Expander Header more data.
I have 2 tables (Document 1 - * Entry).
I'm displaying the Entries grouped by Documents and I don't want some data to be repeated in the
datagrid so I thought to place it in the expander header.
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text=" - "/>
**<TextBlock Text="{Binding Path=Document.Number or Name2}"/>**
</StackPanel>
...
You can do this:
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupItem}}, Converter={StaticResource ResourceKey=groupToTitleConverter}}" />
</StackPanel> </Expander.Header>
Converter:
public class GroupToTitleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
GroupItem groupItem = value as GroupItem;
CollectionViewGroup collectionViewGroup = groupItem.Content as CollectionViewGroup;
EntryViewModel entryViewModel = collectionViewGroup.Items[0] as EntryViewModel;
string title = string.Format("{0} - {1} {2}", entryViewModel.Id, entryViewModel.Numar, entryViewModel.Obiect);
return title;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Taking first item from collection in group to form header title might not be the most elegant solution but it will serve the purpose.
Full code is available here: ExpanderHeadersInDataGridGroupStyle.zip

wpf listbox checkbox combination, really stuck

I am trying to link the checkbox and listview together, and then use the binding method to set the path to the object in order to set the check-box IsChecked status in the listbox.
List<Test1> datas = new List<Test1>();
var data = new Test1 {Key = 1, Value = "Hello", IsSelected= true};
datas.Add(data);
data = new Test1 {Key = 2, Value = "Hello2", IsSelected= false};
datas.Add(data);
What I need to happen is that if the checkbox is checked ( IsSelected is true ) then I need to populate with those values and then when I click and unclick the checkbox in the GUI I need it to also select the proper listeview item so I can get to the tag property.
This code below does not set the checkbox IsChecked.
<ListBox ItemsSource="{Binding}" Name="lstSwimLane" Width="225" Height="125" SelectionChanged="lstSwimLane_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected, Mode=TwoWay}"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked" />
<TextBlock Text="{Binding Path=Value}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What do I need to change in order to set the check box to the value in the object? I even tried INotifyChange etc...
Here is the object as well.
public class Test1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isChecked;
public int Key { get; set; }
public string Value { get; set; }
public bool IsSelected
{
get { return _isChecked; }
set
{
if(_isChecked != value)
{
_isChecked = value;
OnPropertyChanged("IsSelected");
}
}
}
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I am new to all this wpf, and I am stumped.
Thanks.
Do you need this to work "three-way"? So setting any of the three properties
ListBoxItem.IsSelected
CheckBox.IsChecked
Item1.IsSelected
will affect both of the other properties? Unfortunately, there is no such Binding in WPF so you're gonna have to do a little extra work.
Update
As pointed out by Robert Rossney, a much better solution for this is to bind
ListBoxItem.IsSelected to Item1.IsSelected
CheckBox.IsChecked to ListBoxItem.IsSelected
Updated Xaml
<ListBox ItemsSource="{Binding}"
Name="lstSwimLane"
SelectionMode="Multiple"
Width="225" Height="125" SelectionChanged="lstSwimLane_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected,
Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}},
Path=IsSelected}">
</CheckBox>
<TextBlock Text="{Binding Path=Value}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In case anyone is interested, here is the markup for the listbox / combox. It will display horizontal.
Thank you all again for all you help as I greatly appreciated it.
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding}"
Name="lstSwimLane"
SelectionMode="Multiple"
Width="auto"
Height="auto"
Background="Transparent"
BorderThickness="0"
SelectionChanged="lstSwimLane_SelectionChanged" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=IsChecked, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="3,3,3,3">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked"
VerticalAlignment="Center"
Margin="0,0,4,0" />
<TextBlock Text="{Binding Value}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Resources