WPF: Binding not working inside ContentControl - wpf

I've been on this for hours now and I am not getting what is not working or how it is working. A bit of explanation would be appreciate how all this behave.
I'm trying to add a trigger based on another cell content and it is working fine if I forget the binding part.
My problem is actually the Binding itself. It simply doesn't work if it's inside the ContentControl.
My Code:
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding CBW_Type}" Value="Text">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Path=CBW_Content, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
I tried the below and it work as expected (Without the trigger). The data get into place just fine.
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=CBW_Content, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
I searched a lot of quite a few have solved this with the UpdateSourceTrigger=PropertyChanged but it didn't work for me.
Anyone can tell what is not working once I embed the binding inside the ContentControl?
Regards,

Just found the answer. I needed to add so the ContentControl is not empty I guess. If anyone can put a light on that one, I'd appreciate it.
The DataContext of the ContentTemplate of a ContentControl is the Content of the ContentControl. So for your binding to the CBW_Content property to work, you need to set or bind the Content property of the ContentControl to an instance of an object where the CBW_Content property is defined.
In this case this is the corresponding object in the ItemsSource collection of the DataGrid. That's why <ContentControl Content="{Binding}"> works.
If you don't set or bind the Content property so something, there is nothing to bind to and that's why your DataTrigger didn't work.
Hopes that makes sense.

ContentTemplate is to provide a different template for the Content, no Content => ContentTemplate clueless. So, you can also replace ContentTemplate with Content and remove DataTemplate like this in your original code :
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding Path=CBW_Content, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</Setter.Value>
</Setter>

Related

How to create ToolTip for DataGridColumnHeader in WPF?

I have found how to create ToolTip for DataGridColumnHeader in WPF (and make sure that the Header is wrapped).
<Style x:Key="StartingTabToolTipHeaderStyle" TargetType="DataGridColumnHeader">
<Setter Property="ToolTip" Value="{x:Static r:Resource.StartingTabToolTip}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I can use this style in this way:
<DataGridComboBoxColumn Header="{x:Static r:Resource.StartingTab}" SelectedItemBinding="{Binding StartingTab}"
HeaderStyle="{StaticResource StartingTabToolTipHeaderStyle}">
This solution is not nice because I need to create a separate style for each header because the tooltip is hardwired in the style. I would like a general style which can be used in this way:
<DataGridComboBoxColumn Header="{x:Static r:Resource.StartingTab}" SelectedItemBinding="{Binding StartingTab}"
MyToolTip="{x:Static r:Resource.StartingTabToolTip}">
Any idea how to do it? May be with attached property?
You can set the ToolTipService.ToolTip attached property on each column and bind your values to it, because it is not used on columns.
<DataGridComboBoxColumn Header="{x:Static r:Resource.StartingTab}"
SelectedItemBinding="{Binding StartingTab}"
HeaderStyle="{StaticResource StartingTabToolTipHeaderStyle}"
ToolTipService.ToolTip="{x:Static r:Resource.StartingTabToolTip}">
Then you can bind this text on the column in your header style using a RelativeSource binding.
<Style x:Key="StartingTabToolTipHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ToolTip" Value="{Binding Column.(ToolTipService.ToolTip), RelativeSource={RelativeSource Self}}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" Text="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Now you only have a single style. If you want to avoid setting the style on each column, make it an implicit style by omitting the x:Key. Then it will be applied to all matching types in scope.
<Style TargetType="{x:Type DataGridColumnHeader}">
You could use DataGridTemplateColumn
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="yourHeader" HeaderStyle={StaticResource HeaderStyle1}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--you can add any control such as ComboBox, Button etc-->
<TextBlock Text="{Binding Message}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<!--Editing cell template is not mandatory, only if you want allow user to edit a particular cell, shown here only for reference-->
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!--When the user double clicks on this cell, it changes the control to TextBox (from TextBlock - see above) to allow for user input-->
<TextBox Text="{Binding Message}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Second Column-->
<DataGridTemplateColumn Header="secondHeader" HeaderStyle={StaticResource HeaderStyle2}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemSource="{Binding SomeSource}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This might seem a bit much for each column. But it gives you far more control & better scalability for future complex changes. Its somewhat trivial work put in during the initial dev phase.
Atleast thats my opinion since after the success of the concept, several further inputs/changes & new requirements come in to improve user interactivity with specific rules & logic on how/when the user can interact with UI Elements especially when altering data.
Edit -
Your style can be exactly like you made i.e. targeting DataGridColumnHeader.

Click/focus on a ListBoxItem's content doesn't bubble up

I've got a ListBox that's declared like this:
<ListBox ItemsSource="{Binding Contracts}" SelectedItem="{Binding SelectedContract}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Content="{Binding Name}">
<ListBoxItem.ToolTip>
<Grid>
[code omitted for reasons of clarity]
</Grid>
</ListBoxItem.ToolTip>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I expected the normal selection behavior since I play with the item's ToolTip rather than its content structure.
However, clicking an item's name doesn't focus/select that item. Only by clicking that tiny space between each item (easiest way would be the space between an item's name and the ListBox's border) the item gets focused/selected.
Of course, I googled around and thought I'd found the culprit (event doesn't bubble up). But any solution provided here on SO or elsewhere, e. g. adding code like this:
<ListBoxItem.Style>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListBoxItem.Style>
turned out to not solve the problem. So, I assume I do something wrong and I'm just too blind to see it. And while there might be solutions using code-behind, I prefer to stick with clean and pure XAML.
Please help me, understanding my mistake and solving it.
if the purpose is add ToolTip for ListBoxItem, you can use ItemContainerStyle. ListBox creates ListBoxItems for each databound item, adding ListBoxItem into
DataTemplate isn't necessary, if it breaks some functionality, try to avoid it
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="ToolTip">
<Setter.Value>
<Grid>
<TextBlock Text="{Binding .}"/>
</Grid>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
edit: I used Snoop app to check your variant with ListBoxItem in DataTemplate. There is 2 ListBoxItems in visual tree of each ListBox element, maybe one of prevent selection of another

Property 'Content' was not found in type 'FrameworkElement' using DataTrigger

I'm new to WPF.
I'm trying to display a ToolTip on a ListBox item only when the grouping is equal to "Search Results".
I'm getting an error that says :
"Property 'Content' was not found in type 'FrameworkElement'."
Can anyone tell me what's wrong with the code below?
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=code}">
<TextBlock.ToolTip>
<ToolTip>
<ToolTip.Triggers>
<DataTrigger Binding="{Binding Path=grouping}" Value="Search Results">
<Setter Property="Content" Value="{Binding Path=grouping}"/>
</DataTrigger>
</ToolTip.Triggers>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It works ok without the trigger like the code below so it confuses me why it says that the property was not found.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=code}">
<TextBlock.ToolTip>
<ToolTip Content="{Binding Path=grouping}" />
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The Triggers collection of FrameworkElement is only for event triggers, not for DataTriggers or PropertyTriggers. Define a style for the ToolTip which contains the DataTrigger:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=code}">
<TextBlock.ToolTip>
<ToolTip>
<Tooltip.Style>
<Style TargetType="ToolTip">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=grouping}" Value="Search Results">
<Setter Property="Content" Value="{Binding Path=grouping}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToolTip.Style>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
MSDN says:
Note that the collection of triggers established on an element only
supports EventTrigger, not property triggers (Trigger). If you require
property triggers, you must place these within a style or template and
then assign that style or template to the element either directly
through the Style property, or indirectly through an implicit style
reference.
That doesn't describe your problem directly, but read it as: Set Triggers in styles.
This article gets more specific: Dr. WPF Blog
There is also a Triggers collection on FrameworkElement, but it can
only contain event triggers… not property or data triggers.

Different views / data template based on member variable

I have a view model called
ViewModelClass
which contains a boolean.
I have another view model which contains
ObservableCollection<ViewModelClass> m_allProjects;
Then I have this in my view:
<DataTemplate>
<views:ProjectInfoView x:Key="ProjectInfoDetailTemplate"/>
</DataTemplate>
<ItemsControl Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding AllProjects}"
ItemTemplate="{StaticResource ProjectInfoDetailTemplate}"
Margin="10,28.977,10,10">
</ItemsControl >
I want, based on the boolean in the AllProjects-collection, to use a different datatemplate. What is the best way to do this?
I know I can do this with different ViewModels and use a kind of ViewModel-base object, but I prefer just to use 1 view model.
EDIT:
I want to do this with data triggers. Can someone provide me with some code please?
I usually use a ContentControl to display the data, and swap out the ContentTemplate in a trigger based on the property that changes.
Here's an example I have posted on my blog that swaps a template based on a bound property
<DataTemplate x:Key="PersonTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Person" />
</DataTemplate>
<DataTemplate x:Key="BusinessTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Business" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ConsumerViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PersonTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ConsumerType}" Value="Business">
<Setter Property="ContentTemplate" Value="{StaticResource BusinessTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
A DataTemplateSelector will also work, but only if the property that determines which template to show doesn't change since DataTemplateSelectors don't respond to change notifications. I usually avoid them if possible since I also prefer my view selection logic in my view so I can see whats going on.

Conditionally changing the Foreground of a WPF ComboBox item depending upon a value in the databound ItemsSource item

I have a WPF ComboBox bound to a Collection List<Users>. I have applied a DataTemplate to show the FirstName using a TextBlock and this works as expected:
<ComboBox Margin="5" ItemsSource="{Binding Path=TheUsers}" Name="cboUsers">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="10" Text="{Binding Path=FirstName}">
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>`
I have an item in my User class called IsActive which is a Boolean value. If true then I want to set the Foreground of the TextBlock to Navy.
I have spent so much time on what should be so easy and looked all over the web but most articles talk about changing the overall colour or binding to another element in the xaml.
I tried implementing a DataTrigger and after an hour removed the code because it was not working. It would not recognise my field name. Does anyone have a very simple guide to how to do this or what would be the best approach?
As you apparently are not dealing with fields after all, this style should do what you want:
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Foreground" Value="Navy"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
It would not recognise my field name.
You cannot bind to fields, end of story.

Resources