DataTemplate, LoadContent and DataTriggers not firing - wpf

I have a custom UserControl which has a DependencyProperty "ItemTemplate" of type "DataTemplate". I create an instance of this ItemTemplate via .LoadContent(), assign the .DataContext and put it an ContentControl. The only drawback I have is that DataTemplate.Triggers are not fired.
Example Xaml Code:
<Window.Resources>
<DataTemplate x:Key="MyTemplate">
<Label Name="MyLabel" Content="Default"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="1">
<Setter TargetName="MyLabel" Property="Content" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="0">
<Setter TargetName="MyLabel" Property="Content" Value="False" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<ContentControl x:Name="MyContent" />
Example Code Behind:
private void Window_Loaded(object sender, RoutedEventArgs e) {
var template = FindResource("MyTemplate") as DataTemplate;
var instance = template.LoadContent() as FrameworkElement;
instance.DataContext = "1";
MyContent.Content = instance;
}
Output is "Default".
The same DataTemplate used in a ListBox works fine:
<ListBox x:Name="MyListBox" ItemTemplate="{StaticResource MyTemplate}" />
Code Behind:
MyListBox.ItemsSource = new[] { "1", "0" };
Output is "True" and "False".
Any ideas how to fire the DataTemplate.Triggers? Do I need to manually cycle over all triggers and execute them? If yes how can I evaluate a trigger?
Thanks in advance,
Christian

Apply your DataTemplate to a ContentControl rather than modifying it on the fly like you are:
MyContent.ContentTemplate = FindResource("MyTemplate") as DataTemplate;
MyContent.Content = "1";

Related

Data Trigger binding another element in different visual tree node

How to modify style of one element in a visual tree, based on style in triggers in another element in different visual tree node
For example,I am having a list of colors,
ColorList = new List<ColorViewModel>();
ColorList.Add(new ColorViewModel() { ColorCode = "#FF0000", ColorName="Red" });
ColorList.Add(new ColorViewModel() { ColorCode = "#00FF00", ColorName="Green" });
ColorList.Add(new ColorViewModel() { ColorCode = "#0000FF", ColorName="Blue" });
this.DataContext = this;
I have the colors show in a ItemsControl and their name in another ItemsControl, When I hover on their name, I want to increase the size of color box for the corresponding color.
I tried setting the triggers based on element name, but since the scope is different. The following is the sample code, that covers my complex scenario. Is there a xaml way to overcome this? Any help appreciated.
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding ColorList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="20" Height="20" Fill="{Binding ColorCode}">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ColorName, Path=IsMouseOver}" Value="True">
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="30"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding ColorList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="ColorName" Text="{Binding ColorName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
It would be possible using XAML only, if UIElement.IsMouseOver would have a setter. Since it is read-only it can't be target of a Binding. That IsMouseOver is read-only makes perfect sense, as it is intended to be set internally solely on mouse input.
Because of this, it is required to either extend ListBox (or ItemsControl) or to implement an attached behavior.
In order to transport the IsMouseOver flag information between both controls, you can add a dedicated property to the data model. The data model (or DataContext) is the only link between both item controls.
In the following example, this property is expected to be of the following definition:
private bool isPreviewEnabled;
public bool IsPreviewEnabled
{
get => this.isPreviewEnabled;
set
{
this.isPreviewEnabled = value;
OnPropertyChanged(nameof(this.IsPreviewEnabled));
}
}
Note that the model should implement INotifyPropertyChanged.
The following example is an attached behavior, that delegates the mouse over flag by providing an attached property as binding target.
Element.cs
public class Element : DependencyObject
{
#region IsMouseOver attached property
public static readonly DependencyProperty IsMouseOverElementProperty = DependencyProperty.RegisterAttached(
"IsMouseOver",
typeof(bool?),
typeof(Element),
new FrameworkPropertyMetadata(
default(bool?),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnBindngAttached));
public static void SetIsMouseOver(DependencyObject attachingElement, bool? value) => attachingElement.SetValue(Element.IsMouseOverProperty, value);
public static bool? GetIsMouseOver(DependencyObject attachingElement) => (bool) attachingElement.GetValue(Element.IsMouseOverProperty);
#endregion
private static void OnBindngAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Only listen to mouse events when the binding initializes the property.
// This guarantees single subscription.
if (d is FrameworkElement frameworkElement && e.OldValue == null)
{
frameworkElement.MouseEnter += DelegateIsMouseEnter;
frameworkElement.MouseLeave += DelegateIsMouseLeave;
}
}
private static void DelegateIsMouseEnter(object sender, MouseEventArgs e)
{
var attachedElement = sender as DependencyObject;
SetIsMouseOver(attachedElement, true);
}
private static void DelegateIsMouseLeave(object sender, MouseEventArgs e)
{
var attachedElement = sender as DependencyObject;
SetIsMouseOver(attachedElement, false);
}
}
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding ColorList}">
<ItemsControl.ItemTemplate>
<DataTemplate">
<Rectangle Fill="{Binding ColorCode}">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="20" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsPreviewEnabled}" Value="True">
<Setter Property="Width" Value="30" />
<Setter Property="Height" Value="30" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding ColorList}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<!--
Delegate the IsMouseOver to the data model,
which is the data context of the item container
-->
<Setter Property="Element.IsMouseOver"
Value="{Binding IsPreviewEnabled}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ColorName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>

WPF Custom Control library : trying to bind to templatedparent property from a keyed resource, ancestor way fails

How can I make this custom control library binding work? The code is heavily simplified for the sake of brevity, the Listview chooses its current View among several Views thank to a Datatrigger in its style, I'm only showing one for the sake of brevity. The failing binding is the GridViewColumn.Width one
Themes/Generic.Xaml :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<DataTemplate x:Key ="myDataTemplate">
<!--This visibility binding works-->
<CheckBox Width="16" Height="16" x:Name="ItemCheckbox"
Visibility="{Binding CheckBoxVisibility,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</DataTemplate>
<GridView x:Name="myView" x:Key="myView" x:Shared="False">
<!--this width binding fails : Cannot find source for binding-->
<GridViewColumn x:Name="NameColumn" Header="Name"
CellTemplate="{StaticResource myDataTemplate}"
Width="{Binding NameColumnWidth,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:CustomControl1}}}" />
</GridView>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ListView ItemsSource="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ItemsSource}" >
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="True">
<Setter Property="View"
Value="{StaticResource myView}" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource TemplatedParent},
Path=ViewDetails}" Value="False ">
<Setter Property="View"
Value="{StaticResource myView2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomControl1.cs
public class CustomControl1 : Control
{
//these properties should be DependencyProperties, simplified here for the sake of brevity
ObservableCollection<Item> items;
public object ItemsSource { get => items; }
//this is the property I fail to bind to
public double NameColumnWidth { get => this.Width; }
//this one works
public Visibility CheckBoxVisibility { get => Visibility.Visible; }
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1),
new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
Edit:
I actually ended up changing my approach and instead of binding the column width to a Property in the templated parent I made it auto resizing to its content by subclassing GridView. This is an overall better solution. If anyone finds a solution to this binding puzzle I'll still be happy to test and accept it.
public class AutoAdjustingGridView: GridView
{
protected override void PrepareItem(ListViewItem item)
{
base.PrepareItem(item);
foreach(var column in Columns)
{
if (double.IsNaN(column.Width))
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}
}

Switch images with a data template

I know that already some questions exist but I can not fix my problem with them.
Problem: I try to change a image with a data template but just the default image is visible.
Code:
My xaml code is like this:
<Window.Resources>
<DataTemplate x:Key="MultiTemplate">
<Image Height="17" Width="17">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Start.svg}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MultiTrigger}" Value="start">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Start.svg}"/>
</DataTrigger>
<DataTrigger Binding="{Binding MultiTrigger}" Value="stop">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Stop.svg}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{DynamicResource MultiTemplate}"/>
In the code behind I set MultiTrigger = "start" or "stop".
Question: Can I show the images with the content control? Or I do some dumb stuff with the data template?
Edit:
public string MultiTrigger
{
get { return _multiTrigger; }
set
{
_multiTrigger = value;
RaisePropertyChanged();
}
}
Assuming that there is a MainViewModel class with a MultiTrigger property (which btw. is a strange property name), you would assign an instance of the view model class to the MainWindow's DataContext, either in code behind:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
Or in XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
Then you would declare the Image Style as a resource:
<Window.Resources>
<Style TargetType="Image" x:Key="ImageStyle">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Start.svg}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MultiTrigger}" Value="stop">
<Setter Property="Source" Value="{svg2Xaml:SvgImage VideoControllerTester;component/Resources/Stop.svg}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
and apply it to an Image control:
<Image Style="{StaticResource ImageStyle}"/>
Then change the property value somewhere in the MainWindow's code behind by directly accessing the view model instance like this:
((MainViewModel)DataContext).MultiTrigger = "stop";

multiple binding to IsEnable

I need to bind a TextBox that meets two criteria:
IsEnabled if Text.Length > 0
IsEnabled if user.IsEnabled
Where user.IsEnabled is pulled from a data source. I was wondering if anyone had a easy method for doing this.
Here is the XAML:
<ContentControl IsEnabled="{Binding Path=Enabled, Source={StaticResource UserInfo}}">
<TextBox DataContext="{DynamicResource UserInfo}" Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding Path=Text, RelativeSource={RelativeSource Self}, Converter={StaticResource LengthToBool}}"/>
</ContentControl>
As GazTheDestroyer said you can use MultiBinding.
You can also acomplish this with XAML-only solution using MultiDataTrigger
But you should switch the conditions cause triggers support only equality
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text.Length}" Value="0" />
<Condition Binding="{Binding Source=... Path=IsEnabled}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
If one of the condition is not met the value be set to its default or value from the style. But do not set local value as it overrides style's and trigger's values.
Since you only need a logical OR, you just need two Triggers to your each of the properties.
Try this XAML:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=InputText, Path=Text}" Value="" >
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=MyIsEnabled}" Value="False" >
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label>MyIsEnabled</Label>
<CheckBox IsChecked="{Binding Path=MyIsEnabled}" />
</StackPanel>
<TextBox Name="InputText">A block of text.</TextBox>
<Button Name="TheButton" Content="A big button.">
</Button>
</StackPanel>
I set DataContext to the Window class which has a DependencyProperty called MyIsEnabled. Obviously you would have to modify for your particular DataContext.
Here is the relevant code-behind:
public bool MyIsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public static readonly DependencyProperty MyIsEnabledProperty =
DependencyProperty.Register("MyIsEnabled", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(true));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
Hope that helps!
Bind IsEnabled using a MultiBinding

"Tag" ... Special functionality in WPF?

MSDN says "Gets or sets an arbitrary object value that can be used to store custom information about this element." which means I can store anything I want in this property.
But if you bind to this property (with property of type String having a value say "XYZ") and use it in Trigger conditions it doesn't work!
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
It does not set the background red. You can try and assume myElement to be a TextBlock! Why is it like this?
Tag has no special functionality in WPF.
This works for me:
<TextBlock Tag="{Binding Data}"
x:Name="tb">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBlock.Tag"
Value="XYZ">
<Setter Property="TextBlock.Background"
Value="Lime" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
And setting the Data object property to "XYZ" in an event.
The Tag is a construct held over from Winforms days (and possibly there from before that!). It was used as a convenient place to associate an object with a UI element, such as a FileInfo with a Button, so in the Button's event handler you could simply take the event sender, cast it to a Button, then cast the Tag value to a FileInfo and you have everything you need about the file you want to open.
There is one situation, however, where I've found the Tag is useful in WPF. I've used it as a holding spot that can be accessed by a ContextMenu MenuItem, which can't use the normal RelativeSource bindings you'd use to traverse the visual tree.
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="Tag"
Value="{Binding ElementName=TheUserControlRootElement}" />
<Setter
Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="_Remove"
ToolTip="Remove this from this list"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding PlacementTarget.Tag.Remove, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
From the ContextMenu, I cannot access the Remove command which is defined in the UserControl class where this snippet is defined. But I can bind the root to the Tag of the ListBoxItem, which I can access via the ContextMenu.PlacementTarget property. The same trick can be used when binding within a ToolTip, as the same limitations apply.
MainWindow.xaml:
<Window x:Class="wpftest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock x:Name="test" MouseDown="test_MouseDown"
Tag="{Binding TestProperty}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
private void test_MouseDown(object sender, MouseButtonEventArgs e)
{
((TestViewModel)DataContext).TestProperty = "XYZ";
}
private sealed class TestViewModel : INotifyPropertyChanged
{
private string _testPropertyValue;
public string TestProperty
{
get { return _testPropertyValue; }
set
{
_testPropertyValue = value;
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("TestProperty"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Updated: Tag property now is bound to TestProperty.

Resources