Text separator between ListView in WPF - 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();
}
}

Related

ListView inside ListView + control.Visibility

I'm creating a questionnaire app. My way of doing this is to create a ListView which contains question text and another ListView which contains list af answers(as RadioButtons).
The problem came when there are question which have an answer "Others" which require a TextBox for user to type some text. How can I achieve this? I mean i want to make TextBox visible only when collection of answers contains RadioButton with content "Other".
Below is my xaml code for ListView.
<ListView SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding OCquestions}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20 0 20 0">
<TextBlock Text="{Binding Path=questionText}"/>
<ListView Name="ListaLista" SelectionChanged="myList_SelectionChanged" ItemsSource="{Binding Path=listOfAnswer}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}" Content="{Binding Path=answerText}" Checked="RadioButton_Checked"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// HERE I WANT A TEXTBOX WHICH IS VISIBLE ONLY WHEN listOfAnswer collection contain a RadioButton with Content "Others"
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have no idea how to achieve this. I'm not familiar with Converters. Can anyone give me some tip ?
You need some Trigger to show/hide the TextBox, something like this:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton GroupName="{Binding Path=questId}"
Content="{Binding Path=answerText}"
Checked="RadioButton_Checked" Name="radio"/>
<TextBox Name="other" Visibility="Collapsed"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding answerText}" Value="Other">
<Setter TargetName="radio" Property="Content" Value=""/>
<Setter TargetName="other" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You can see that the DataTrigger listens to answerText, if it's "Other" just set the Content of Radio to empty string and set the TextBox's Visibility to Visible to show it. This TextBox will be shown on the right of the RadioButton.
First add a ValueConverter:
public abstract class BaseConverter : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
public class AnswerCollectionToVisibilityConverter : BaseConverter, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ICollection<ListOfAnswers> answers = value as ICollection<ListOfAnswers>;
if (answers != null)
{
foreach (Answer answer in answers)
{
if (OtherRadioButtonIsHere)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then add a TextBox that uses the ValueConverter to set the Visibility:
<TextBox Visibility="{Binding Path=listOfAnswer, Converter={AnswerCollectionToVisibilityConverter}}" />

Show tick image when item is selected in WPF Listbox

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}"

WPF: IValueConverter with List

Can use IValueConverter with List. It can use in the first time I call the menu. When I updated the items in list, it don't call IValueConverter again?
Example:
<MenuItem Header="{Binding Path=DataContext.Documents, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListView}}, Converter={StaticResource DocumentsToString}, Mode=OneWay}">
<MenuItem.Icon>
<Image Source="Images/upload.png" Style="{StaticResource ImageContextMenu}"/>
</MenuItem.Icon>
</MenuItem>
And ValueConverters.cs
public class ListDocumentToStringConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var selectedDocuments = (ObservableCollection<Document>) value;
var result = "";
foreach (var document in selectedDocuments)
{
result += document.Name + "\t";
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
To add to fmunkert's comment which is correct, if you are intent to host all of those items within a single MenuItem, you could write something like this:
<MenuItem>
<MenuItem.Header>
<ItemsControl ItemsSource="{Binding Path=DataContext.Documents, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem.Header>
<MenuItem.Icon>
<Image Source="Images/upload.png" Style="{StaticResource ImageContextMenu}"/>
</MenuItem.Icon>
</MenuItem>
This way you don't have to use a converter at all. If you want to change how these items are laid out with respect to each other, such as using tabs in between in your example, then you would want to template the ItemsPanel of the ItemsControl. By default it is a vertical StackPanel. You could change it to a Horizontal StackPanel like so:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Then you can add spacing or a margin to each TextBlock item to achieve however visual you want.

Prevent empty tooltips at a wpf datagrid

I am working on a calendar program, which consists mainly of a WPF DataGrid. As there is not always enough space to display all the entries of a day (which is a DataGridCell), a tooltip with all the entries of the day shell appear at mouse over. This works so far with the code snippet shown below. And now the (little) problem: If there are no entries for a day, no tooltip shell pop up. With the code below an empty tooltip pops up.
<DataGridTemplateColumn x:Name="Entry"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding EntryText}"
Foreground="{Binding EntryForeground}"
FontWeight="{Binding EntryFontWeight}">
</TextBlock>
<TextBlock Text="{Binding RightAlignedText}"
Foreground="Gray"
Background="Transparent">
<TextBlock.ToolTip>
<TextBlock Text="{Binding AllEntriesText}"/>
</TextBlock.ToolTip>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The Databinding is made via
myCalDataGrid.Itemssource = _listOfDays;
in code behind, where a 'Day' is the view model for a single calendar row.
As H.B. suggested bind directly to the ToolTip property instead of using TextBlock and in case AllEntriesText is empty string you can apply a trigger on your TextBlock to disable your tooltip by setting the property ToolTipService.IsEnabled like this -
<TextBlock Text="{Binding RightAlignedText}"
Foreground="Gray"
Background="Transparent"
ToolTip="{Binding AllEntriesText}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="ToolTip"
Value="{x:Static system:String.Empty}">
<Setter Property="ToolTipService.IsEnabled" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Make sure to add namespace system in your xaml -
xmlns:system="clr-namespace:System;assembly=mscorlib"
Bind directly to the ToolTip property (do not create a TextBlock for it) and set AllEntriesText to null if there are no entries, then the ToolTip itself is also null and should not show.
Thanks for the solutions, they may work, no question. But I need a TextBlock for the tooltip to format and align the text. So I found this solution:
<TextBlock Text="{Binding RightAlignedText}"
HorizontalAlignment="Stretch"
TextAlignment="Right" Padding="2,0"
Foreground="Gray"
Background="Transparent"
ToolTipService.ShowDuration="60000"
ToolTipService.BetweenShowDelay="0"
ToolTipService.InitialShowDelay="0"
>
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding EntryToolTipVisibility}">
<TextBlock Text="{Binding ToolTipText}"
TextAlignment="Left"
FontFamily="Courier New"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
I bound the propertie "Visibility" of tooltip to a propertie "EntryToolTipVisibility" (of type Visibility) in the view model. See code snippet below.
public Visibility EntryToolTipVisibility
{
get
{
return _entries.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
Another option is to use an own Converter.
I prefer this way for example for a TextBlock tooltip that displays TextBlock's text, but for the case there is no text, the empty tooltip is not desired.
XAML code:
//step #1
xmlns:local="clr-namespace:MyNamespace"
//step #2 - into Window.Resources or other
<local:StringToVisibleTooltip x:Key="StringToVis" />
//step #3 - example of use
<TextBlock ...other attributes... TextTrimming="CharacterEllipsis">
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding Path=Text, Converter={StaticResource StringToVis}}">
<TextBlock Text="{Binding Text}"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
And code behind
namespace MyNamespace
{
public class StringToVisibleTooltip : IValueConverter
{
public StringToVisibleTooltip() { }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && ((string)value).Length > 0) //empty string does not need tooltip
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}

Use different template for last item in a WPF itemscontrol

I'm using a custom template in my itemscontrol to display the following result:
item 1, item 2, item3,
I want to change the template of the last item so the result becomes:
item 1, item2, item3
The ItemsControl:
<ItemsControl ItemsSource="{Binding Path=MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=", "/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Is there anyone who can give a solution for my problem? Thank you!
I've found the solution for my problem using only XAML. If there is anybody who needs to do the same, use this:
<ItemsControl ItemsSource="{Binding Path=MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="comma" Text=", "/>
<TextBlock Text="{Binding}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter TargetName="comma" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can use DataTemplateSelector, in SelectTemplate() method you can check whether item is the last and then return an other template.
In XAML:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter
ContentTemplateSelector = "{StaticResource MyTemplateSelector}">
In Code behind:
private sealed class MyTemplateSelector: DataTemplateSelector
{
public override DataTemplate SelectTemplate(
object item,
DependencyObject container)
{
// ...
}
}
This solution affects the last row and updates with changes to the underlying collection:
CodeBehind
The converter requires 3 parameters to function properly - the current item, the itemscontrol, the itemscount, and returns true if current item is also last item:
class LastItemConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int count = (int)values[2];
if (values != null && values.Length == 3 && count>0)
{
System.Windows.Controls.ItemsControl itemsControl = values[0] as System.Windows.Controls.ItemsControl;
var itemContext = (values[1] as System.Windows.Controls.ContentPresenter).DataContext;
var lastItem = itemsControl.Items[count-1];
return Equals(lastItem, itemContext);
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
The Data-Trigger for a DataTemplate, that includes a textbox named 'PART_TextBox':
<DataTemplate.Triggers>
<DataTrigger Value="True" >
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource LastItemConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" />
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" Path="Items.Count"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" TargetName="PART_TextBox" Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
The converter as a static resource in the Xaml
<Window.Resources>
<local:LastItemConverter x:Key="LastItemConverter" />
</Window.Resources>
SnapShot
And a snapshot of it in action
The code has been added to the itemscontrol from this 'codeproject'
https://www.codeproject.com/Articles/242628/A-Simple-Cross-Button-for-WPF
Note the last item's text in red
One question... I see you're using an ItemsControl as opposed to say a ListBox and that it appears to be bound to a collection of strings, and that you're only trying to display the resulting text without formatting the individual parts, which makes me wonder if your desired output is actually the string itself as mentioned in the question, and not an actual ItemsControl per se.
If I'm correct about that, have you considered just using a simple TextBlock bound to the items collection, but fed through a converter? Then Inside the converter, you would cast value to an array of strings, then in the Convert method, simply Join them using a comma as the separator which will automatically, only add them between elements, like so...
var strings = (IEnumerable<String>)value;
return String.Join(", ", strings);

Resources