datatemplate not switching upon datatrigger - wpf

I have defined two datatemplates for my listBox for which I am using a DataTemplateSelector to decide which one to be rendered for an item. However, One of my templates has an embedded button which when clicked should display a form in place. So I have setup a boolean property and implemented INotifyPropertyChanged; so that when the boolean property is true, the current template is replaced by a third template.
This is the code for the button.
Activity activityItem = (Activity)listBox1.Items[listBox1.SelectedIndex];
activityItem.ShowFeedbackForm = 1;
This is my XAML:
<Grid.Resources>
<DataTemplate x:Key="completedActivityTemplate">
<Grid Name="templateGrid" >
...
<DataTemplate x:Key="activityTemplate">
<Grid Name="templateGrid" >
...
<DataTemplate x:Key="feedbackFormTemplate">
<Grid Name="templateGrid" >
...
<Style TargetType="ListBoxItem" x:Key="ContainerStyle">
<Setter Property="Height" Value="55" />
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
<DataTrigger Binding="{Binding showFeedbackForm}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource feedbackFormTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox Grid.Row="1" Name="listBox1" IsSynchronizedWithCurrentItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VerticalContentAlignment="Stretch" Background="Transparent"
BorderThickness="0" ItemContainerStyle="{StaticResource ContainerStyle}"
HorizontalContentAlignment="Stretch" Margin="-3,0,0,0"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}">
</ListBox>
So when the user clicks the button, the value of 1 is set for the ShowFeedbackForm for that item which is when the template showing a feedback form is to be displayed but nothing is happening. My ObservableCollection(one way) bindings are working fine.

In the button click handler, I set the boolean value the bound item and then rebind the datatemplate selector for the listBox.
ListBoxItem currentItem = (ListBoxItem)(listBox1.ItemContainerGenerator.ContainerFromItem(listBox1.Items.CurrentItem));
Activity activityItem = (Activity)listBox1.Items[listBox1.SelectedIndex];
activityItem.ShowFeedbackForm = 1;
listBox1.ItemTemplateSelector = new ActivityFeedDataTemplateSelector();
My ActivityFeedDataTemplateSelector returns the desired template name if the boolean is set.

Related

WPF: DataTrigger for Topmost property of Prism Window XAML

I am trying to control the Topmost property of a prism popup window with a DataTrigger, so that when a button within the XAML file is pressed, the Topmost property is set to false. I am pretty new to WPF and am not sure what I need to do to get my binding to work.
Note that the IsModal property is set to false and I'd like to keep it that way, unless the IsModal feature could also be styled with a DataTrigger from the button.
Here's the prism Popup with the style and attempted trigger
<prism:InteractionRequestTrigger SourceObject="{Binding DataBarChartRequest, Mode=OneWay}">
<prism:PopupWindowAction IsModal="False" WindowStartupLocation="CenterScreen">
<prism:PopupWindowAction.WindowStyle>
<Style TargetType="{x:Type Window}">
<Setter Property="Topmost" Value="True"/>
<Setter Property="Height" Value="650"/>
<Setter Property="Width" Value="900"/>
<Setter Property="ResizeMode" Value="CanResize"/>
<Setter Property="ShowInTaskbar" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=StopButton, Path=IsPressed}" Value="True">
<Setter Property="Window.Topmost" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</prism:PopupWindowAction.WindowStyle>
<prism:PopupWindowAction.WindowContent>
<local:DataBarChartNotificationView Width="NaN" Height="NaN" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</prism:PopupWindowAction.WindowContent>
</prism:PopupWindowAction>
</prism:InteractionRequestTrigger>
and here's the button
<RibbonButton Label="Stop"
x:Name="StopButton"
Command="{Binding StopSessionCommand}"
LargeImageSource ="{StaticResource SessionStopGray}"
FontSize="12"
FontWeight="Regular"
Height="60"
Margin="0,10,0,0"
Foreground="White"/>
It seems as though, despite this InteractionRequestTrigger being in the same xaml file as my button, the prism popup cannot find this local source, or it simply won't work. Would like to know if I can style either the IsModal or Topmost property with the button in the XAML file as a DataTrigger or how to fix my binding to do so.

How to set a property of a different control from a style setter

I have a ListView and inside this another ListView. Whenever I select an item in a child ListView I want the parent of that to be selected in the parent ListView. Example:
<Window>
<Window.Resources>
<!-- Parent ListView ItemsTemplate... Incomplete -->
<DataTemplate x:Key="parentItemTemplate">
<!-- Child ListView -->
<ListView SelectedItem="{Binding ChildSelectedItem}" ItemsSource="{Binding WhateverInParent}">
<ListView.Resources>
<Style TargetType="ListViewItem">
<Trigger Property="IsSelected" Value="True">
<!-- This is what I want to do, but ofc this doesn't work because it produces a compile error saying can't set TargetName in a setter -->
<Setter TargetName="parent" Property="SelectedValue" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ListView}}" />
</Trigger>
</Style>
</ListView.Resources>
</ListView>
</DataTemplate>
</Window.Resources>
<ListView ItemsTemplate="{StaticResource parentItemTemplate}" x:Name="parent" SelectedItem="{Binding ParentSelectedItem}" ItemsSource="{Binding Whatever}"/>
</Window>
How do I get this done? Would prefer it to be in XAML.
You just need to set the ListViewItem.ItemContainerStyle like below to achieve what you want
<ListView ItemsTemplate="{StaticResource parentItemTemplate}" x:Name="parent" SelectedItem="{Binding ParentSelectedItem}" ItemsSource="{Binding Whatever}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="IsSelected" Value="true" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
You could simply use the InnerControl class to achieve this.
References:
WPF Nested ListViews - selectedValue of parent ListView
Or else you can go for RelativeSource:
How to access controls parent's property in a style
Hope it helps!

WPF combobox selected index

I have a combobox which I'm setting it's itemsSource after getting it from a database, for some reason the combobox doesn't show the first item.
My comboBox:
<ComboBox Text="Add To Message:" ItemsSource="{Binding Messages}" SelectedValue="{Binding SelectedMessage}" Style="{StaticResource comboStyle}"/>
My style:
<Style TargetType="ComboBox" x:Key="comboStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItemId}" Value="-1">
<Setter Property="SelectedIndex" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
Instead of setting SelectedIndex you should set SelectedMessage:
// get data from db...
SelectedMessage = Messages.FirstOrDefault();

Datatrigger on contentpresenter.content not working

I am trying to switch the content of a contentpresenter based on a datatrigger.I want to display a usercontrol in the contentpresenter.content, if i have a value set or else i need to display an error message.But the binding on my datatrigger fails stating that the property is not found.I cant get the datacontext to inherit for the datatrigger checking.I can make it work by using the commented out code.But i am confused why it doesn't work the normal way.
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Content" Value="{Binding UC}"/>
<Style.Triggers>
<!--<DataTrigger Binding="{Binding DataContext.HasValue,RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}}}" Value="false">
<Setter Property="Content" Value="No preview"/>
</DataTrigger>-->
<DataTrigger Binding="{Binding HasValue}" Value="false">
<Setter Property="Content" Value="No value"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
If you want to use triggers to display UserControl, you should use ContentControl not ContentPresenter.
I prefer to use ContentPresenter for CustomControls and When I am using the UserControl for views of Custom Data Types in my system and Allow to give dynamic behavior.
Example: To switch templates for ContentPresenter you need to set ContentTemplateSelector like this
<ContentPresenter Content="{Binding MyContent}"
ContentTemplate="{Binding MyContentTemplate}"
ContentTemplateSelector="{Binding MyContentTemplateSelector}"/>
MyContent, MyContentTemplate & MyContentTemplateSelector are Dependency Properties and can be binded wherever you are using its instance.
READ :
Usage of ContentPresenter
What is the difference between ContentControl and ContentPresenter
The binding mentioned in the question won't work as
ContentPresenter’s DataContext is automatically set to the value of
its Content property, while ContentControl’s DataContext is not.
Bindings are resolved relatively to the value of the DataContext property. If you declare a binding on the ContentPresenter, the moment its content is set, the binding would be re-evaluated.
ContentControl.Content Property can be changed on any trigger based on your requirement. If you want to use it to change on PropertyChanged Event of a property of ViewModel, DataTrigger can be used by binding it with a DataTemplate with UserControl instance in it or using static resource of that UserControl.
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentTemplate" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentContro.Style>
</ContentControl>
READ How to use triggers for content template, more details here
Difference in DataTemplate and StaticResource scope is DataTemplate creates a new instance of the template every time its applied.
Whereas, StaticResource is using the same instance of UserControl again (Static Instance).
You can also use EventTriggers to change content base don Control Events like MouseOver etc.
Alternate approach
Very similar to the above with slight difference. Defining as a data template in resources. Triggering for the content change is essentially identical.
...in <x.Resources /> tag:
<DataTemplate x:Key="DesignerTemplate" DataType="{x:Type vm:SolutionViewModel}">
<vw:SolutionDesignerView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SolutionViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoaded}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource DesignerTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
...then:
<ContentControl Content="{Binding Solution}" />
I usually use trigger Like this...
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="normalTemplate" >
<!-Fav UserControl->
</DataTemplate >
<DataTemplate x:Key="overWriteTempalte">
<!-Fav UserControl-> </DataTemplate>
</UserControl.Resources>
<ContentPresenter x:Name="ContentField"
Content="{Binding}"
ContentTemplate="{StaticResource ResourceKey=normalTemplate}" />
<UserControl.Triggers>
<DataTrigger Binding="{Binding Path=MyProperty}" Value="True">
<Setter TargetName="ContentField" Property="ContentTemplate" Value="{StaticResource ResourceKey=overWriteTempalte}" />
</DataTrigger>
</UserControl.Triggers>
</UserControl>
If Bindings are a problem Use Snoop to Detect binding errors

Change ListView.ItemTemplate on subelement change

let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger

Resources