DataBinding ListView creating components if a certain condition is met - c#

I am working on a C# WPF project and I have a list view which uses data binding and a collection view.
Below is how my ListView is currently populated.
<ListView HorizontalAlignment="Left" Margin="547,12,0,41" Name="lstCallLogInformation" Width="320">
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListView">
<TextBlock Text="No items in your devices call log" FontWeight="Bold" HorizontalAlignment="Center" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Foreground="Gray" Text="{Binding Name}" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel Style="{StaticResource onmouseover}">
<StackPanel HorizontalAlignment="Stretch" Orientation="Horizontal" Tag="{Binding logID}">
<Image Height="30" Source="{Binding base64ImageString, Converter={StaticResource ImageConverter}}" Width="30" />
<StackPanel Margin="10" Orientation="Vertical">
<TextBlock FontWeight="Bold" Text="{Binding contactNameOrPhoneNumber}" />
<StackPanel HorizontalAlignment="Stretch" Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" Text="{Binding dateString}" />
<TextBlock Margin="15, 0, 0, 0" Text="Duration: " />
<TextBlock HorizontalAlignment="Right" Text="{Binding humanReadableCallDuration}" />
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical">
<Button Background="Transparent" BorderThickness="0" BorderBrush="Transparent" Tag="{Binding logID}" Name="btnAddAsContact" Click="btnAddAsContact_Click">
<Image Width="15" Height="15" Source="/BoardiesSMSServer;component/images/add.png" Tag="{Binding logID}" />
</Button>
<Button Background="Transparent" BorderThickness="0" BorderBrush="Transparent" Tag="{Binding logID}" Name="btnDeleteContactFromLog" Click="btnDeleteContactFromLog_Click">
<Image Width="15" Height="15" Source="/BoardiesSMSServer;component/images/delete.png" Margin="0,0,0,5" Tag="{Binding logID}" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Within my DockPanel within the DataTemplate I have a StackPanel which contains various TextBlocks from the array that is passed into the binding.
In one of the StackPanels you will see you will see a TextBlock that binds to the element of contactNameOrPhoneNumber.
What I want to be able to do is if another value from the binding is null then I add a TextBlock for the contactNameOrPhoneNumber` element and alongside it have another TextBlock which contains another element of number``, if the other binding value is not null then this extra textblock of number is not added.
So in simple terms, what I want to be able to do is within the DataTemplate have a conditional if statement of if binding value == null add textblock to stackpanel else don't add textblock.

You can do that with Style + Triggers something like this:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding PropertyName}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Also you can create IValueConverter say ObjectToVisibilityConverter which will take input your property and if property is null return Visibility.Collapsed else return Visibility.Visible.
You can then bind property with Visibility value of TextBlock via Converter.
<TextBlock Visibility="{Binding PropertyName,
Converter={StaticResource ObjectToVisibilityConverter}}"/>

Related

Wpf textbox validation controltemplate trigger on text value

I have a textbox:
<TextBox Grid.Row="3"
Grid.Column="1"
Width="200"
TextWrapping="Wrap"
VerticalAlignment="Top"
Margin="5,10,-10,2"
Style="{StaticResource TextBoxValueStyle}"
Validation.ErrorTemplate="{StaticResource ValidationControlTemplate}">
<TextBox.Text>
<MultiBinding StringFormat=" {0} {1}">
<Binding Path="Id" ValidatesOnNotifyDataErrors="True" NotifyOnValidationError="True"/>
<Binding Path="Name" />
</MultiBinding>
</TextBox.Text>
</TextBox>
And here is my controltemplate:
<ControlTemplate x:Key="ValidationControlTemplate">
<DockPanel Visibility="{Binding ElementName=Placeholder, Path=Visibility}">
<Image x:Name="Image"
DockPanel.Dock="Right"
VerticalAlignment="Center"
Margin="0,-2"
Style="{StaticResource InformationImageStyle}">
<Image.ToolTip>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Image.ToolTip>
</Image>
<AdornedElementPlaceholder Name="Placeholder" VerticalAlignment="Center"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="TextBox.Text" Value="">
<Setter Property="DockPanel.Dock" TargetName="Image" Value="Left"/>
<Setter Property="Margin" TargetName="Image" Value="-20,-2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
My issue is that when the textbox has a value (and has error)it is NOT taking the original values (DockPanel.Dock=Right and Margin=0,-2)
I am always starting with text empty. The image is always showing to the left, on error.
What am I missing?
Put a Grid around the <AdornedElementPlaceholder /> element and replace the Trigger with a DataTrigger that binds to the adorned TextBox element:
<ControlTemplate x:Key="ValidationControlTemplate">
<DockPanel Visibility="{Binding ElementName=Placeholder, Path=Visibility}">
<Image x:Name="Image"
DockPanel.Dock="Right"
VerticalAlignment="Center"
Margin="0,-2"
Style="{StaticResource InformationImageStyle}">
<Image.ToolTip>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Image.ToolTip>
</Image>
<Grid>
<AdornedElementPlaceholder Name="Placeholder" VerticalAlignment="Center"/>
</Grid>
</DockPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding AdornedElement.Text.Length, ElementName=Placeholder}" Value="0">
<Setter Property="DockPanel.Dock" TargetName="Image" Value="Left"/>
<Setter Property="Margin" TargetName="Image" Value="-20,-2"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>

WPF Adding a datatrigger to the ImageSource of a user control from another control

I have the following usercontrol:
<Border
Style="{StaticResource notificationBarBorderStyle}"
Height="21"
>
<StackPanel
Orientation="Horizontal"
Style="{StaticResource notificationBarStyle}"
>
<DockPanel>
<Image
Width="17"
Height="16"
Margin="4,0,11,0"
Source="{Binding ElementName=NotificationControl, Path=ImageSource}"
/>
<TextBlock
x:Name="notificationTextBlock"
VerticalAlignment="Center"
Style="{StaticResource textBlockStyle}"
Text="{Binding ElementName=NotificationControl, Path=Message}"
/>
</DockPanel>
</StackPanel>
</Border>
and then in another usercontrol i try to reference it like so:
<Controls:NotificationBarControl
Grid.Row="2"
Grid.Column="0"
DataContext="{Binding IncomingResult}"
Message="{Binding TaskResultsMessage}"
Visibility="{Binding Path=ShowTaskResults, Converter={StaticResource boolToHiddenVisibilityConverter}}"
Command="{Binding DisplayTaskError}"
ImageSource="{DynamicResource somePicture1}"
>
I want to be able to put a data trigger on the image source so that depending on the state of a boolean flag, a different image will appear (call it somePicture2). I do not really want to do much to change the control itself, as it is referenced a few times in a rather large project and I do not want to break anything.
You could set the Style property of the control to a Style with a DataTrigger that binds to your bool property:
<Controls:NotificationBarControl
Grid.Row="2"
Grid.Column="0"
DataContext="{Binding IncomingResult}"
Message="{Binding TaskResultsMessage}"
Visibility="{Binding Path=ShowTaskResults, Converter={StaticResource boolToHiddenVisibilityConverter}}"
Command="{Binding DisplayTaskError}">
<Controls:NotificationBarControl.Style>
<Style TargetType="Controls:NotificationBarControl">
<Setter Property="ImageSource" Value="{StaticResource somePicture1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourBooleanSourceProperty}" Value="True">
<Setter Property="ImageSource" Value="{StaticResource somePicture2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Controls:NotificationBarControl.Style>
</Controls:NotificationBarControl>

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.

Templated TabItem header does not show Header content

I got a TabControl with two TabItems, I created a template for it.
<TabControl x:Name="MainInfoTabControl" Grid.Row="3" Grid.RowSpan="7" Grid.Column="0" Grid.ColumnSpan="2"
Background="{x:Null}"
BorderBrush="{x:Null}"
>
<TabControl.Resources>
<Style x:Key="TabHeaderStyle" TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="FontWeight" Value="DemiBold" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="HeaderBorder" BorderBrush="White" BorderThickness="2" CornerRadius="10,10,0,0" >
<TextBlock Text="" VerticalAlignment="Center" Margin="5"></TextBlock>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Main Details"
Margin="0.5, 0.5, 0, 0.5"
FontSize="15"
MinHeight="40" Width="200"
Style="{StaticResource TabHeaderStyle}"
>
</TabItem>
<TabItem Header="Pets Owned"
Margin="0.5, 0.5, 0, 0.5"
FontSize="15"
MinHeight="40" Width="200"
Style="{StaticResource TabHeaderStyle}"
>
</TabItem>
</TabControl>
The problem is the tabItem's Header doesn't show up. In my template there is a TextBlock but i couldn't customize the TextBlock.Text dynamically (atleast bind or somewhat change the Text property during run time)
TabItem1 should be "Owner Details" while TabItem2 should be "Pets Owned"
You override default tabitem template you need to get somehow to parent's value.
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header}"
VerticalAlignment="Center"
Margin="5">
</TextBlock>
or
<TextBlock Text="{TemplateBinding Property=Header}"
VerticalAlignment="Center"
Margin="5">
</TextBlock>

WPF: Disabling Foreground Changes On Selection

I have a WPF that uses a TreeView and a HierarchiacalDataTemplate. When I set styling for the different levels, sometimes the foreground color for the text is White and sometimes its Black (depending on the background color). Here's my dilemma. When the user selects an item in the treeview, wpf applies a border (which is fine) and changes the foreground color of the text. For the items that already have a white foreground, its fine but for the items that have a black foreground the text virtual disappears. I can't really use property setters and triggers because the style doesn't apply universally to all nodes, just to certain ones. How can I disable WPF from changing the foreground color at all OnSelection? Below is my WPF code:
<TreeView Name="tvwConfig" VerticalAlignment="Stretch" DockPanel.Dock="Left" Width="300" Background="LightGray" ItemsSource="{Binding}" SelectedItemChanged="Treeview_SelectedItemChanged" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type cfg:AppConfig}" ItemsSource="{Binding Path=Services}">
<Border Width="200" BorderBrush="DarkBlue" Background="DarkBlue" BorderThickness="1" CornerRadius="2" Margin="2" Padding="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ServerName}" FontWeight="Bold" Foreground="White" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type cfg:Service}" ItemsSource="{Binding Path=Queues}">
<Border Width="200" BorderBrush="RoyalBlue" Background="RoyalBlue" BorderThickness="1" CornerRadius="2" Margin="2" Padding="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="White" />
<TextBlock Text=" (" FontWeight="Bold" Foreground="White" />
<TextBlock Text="{Binding Path=Modality}" FontWeight="Bold" Foreground="White" />
<TextBlock Text=")" FontWeight="Bold" Foreground="White" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type cfg:Queue}" ItemsSource="{Binding Path=Statuses}">
<Border Width="200" BorderBrush="AliceBlue" Background="AliceBlue" BorderThickness="1" CornerRadius="2" Margin="2" Padding="2">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Path=Name}" FontWeight="SemiBold" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type cfg:Status}">
<Border Width="200" BorderBrush="White" Background="White" BorderThickness="1" CornerRadius="2" Margin="2" Padding="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=Weight}" />
<TextBlock Text=")" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I'm not able to test this right now, but I think you should be able to define your Style in an ItemContainerStyle for your HierarchicalDataTemplate and define the style (with setters) in each one as required.
E.g (from one of your examples):
<HierarchicalDataTemplate DataType="{x:Type cfg:AppConfig}" ItemsSource="{Binding Path=Services}">
<!-- Example -->
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Foreground" Value="DarkOrange" />
<!-- Triggers if required -->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="DarkOrange" />
</Trigger>
<!-- DataTriggers if required -->
<DataTrigger Binding="{Binding Path=SomeProperty}" Value="SomeValue">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<!-- End Example -->
<Border Width="200" BorderBrush="DarkBlue" Background="DarkBlue" BorderThickness="1" CornerRadius="2" Margin="2" Padding="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=ServerName}" FontWeight="Bold" Foreground="White" />
</StackPanel>
</Border>
</HierarchicalDataTemplate>
The IsSelected setter should help you sort out the foreground colours.
Set this style in Resources. Don't set any key it will be applied for all the TreeViewItems that satisfy the condition.
Sample code:
<Style TargetType="TreeViewItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>

Categories