MultiDataTrigger Not respecting all conditions - wpf

I have an "On" "Off" switch that I've created and I cannot get the MultiDataTrigger to respect all Conditions.
<UserControl.Resources>
<Style TargetType="Button" BasedOn="{StaticResource NoDevExpressStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Height="30" Width="81" CornerRadius="15" BorderThickness="1" Background="White">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<!-- On Not Selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.On}" />
<Condition Binding="{Binding SwitchClicked, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" Value="False"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OnInactiveColorBrush}"></Setter>
</MultiDataTrigger>
<!-- On Selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.On}" />
<Condition Binding="{Binding SwitchClicked, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" Value="True"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OnColorBrush}"></Setter>
</MultiDataTrigger>
<!-- OFF Not Selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.Off}" />
<Condition Binding="{Binding Path=SwitchClicked}" Value="False"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OffInactiveColorBrush}"></Setter>
</MultiDataTrigger>
<!-- Off and selected -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{ Binding Path=State}" Value="{x:Static models:ExperimentState.Off}" />
<Condition Binding="{Binding Path=SwitchClicked}" Value="True"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush" Value="{StaticResource OffColorBrush}"></Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Button x:Name="Button" Click="ButtonBase_OnClick" />
It seems to respecting the "State" Property which is a dependency property that is set once and never changed. However the SwitchClicked is not respecting which is a property inside the control.
private bool _switchClicked;
public bool SwitchClicked
{
get => _switchClicked;
set => _switchClicked = value;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (Button.Command != null)
{
if (Button.Command.CanExecute(Button.CommandParameter))
{
SwitchClicked = !SwitchClicked;
SetValue(IsActiveProperty, !IsActive);
Button.Command.Execute(null);
}
}
}
Can someone please tell me how to insure that all of my conditions are met?
I have tried binding with relitive source to self and user control, however the SwitchClicked property is in the codebehind and there is no ViewModel.

You have to implement INotifyPropertyChanged either in your view model (or I suppose your code-behind), otherwise setting your property will not notify
being changed and the binding in your trigger will not update.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool SwitchClicked
{
get => _switchClicked;
set
{
if (_switchClicked != value)
{
_switchClicked = value;
OnPropertyChanged(nameof(SwitchClicked));
}
}
}

Related

Change Trigger order in BasedOn / inherited Style

I have a base Style - DataGridRowSelectionStyle. On some DataGrids I need to extend this Style to ink the background.
DataGridRowSelectionStyle
<Style TargetType="DataGridRow" x:Key="DataGridRowSelectionStyle">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsMouseOver}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsSelected}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
RowStyle
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowSelectionStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.OK}">
<Setter Property="Background" Value="{extensions:Theme Key=DGLB_Green}"/>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.NG}">
<Setter Property="Background" Value="{extensions:Theme Key=DGLB_Red}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Because of the Trigger order, the two base Triggers are overwritten and IsMouseOver or IsSelected is not triggered anymore.
Solution 1: Extend the RowStyle. Very bad solution, because I would not need my base Style anymore..
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowSelectionStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.OK}">
<Setter Property="Background" Value="{extensions:Theme Key=DGLB_Green}"/>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.NG}">
<Setter Property="Background" Value="{extensions:Theme Key=DGLB_Red}"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsMouseOver}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsSelected}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Solution 2: Create a behavior and add it to the base Style which would reorder the final Style.
Problem: Behavior<TriggerCollection> or Behavior<Style> is not working!
The type 'System.Windows.Style' must be convertible to 'System.Windows.DependencyObject' in order to use it as paramter 'T' in the generic class 'System.Windows.Interactivity.Behavior'
Someone got a solution how to use a behavior in a Style or how to change the trigger order in the inherited Style?
I got a solution by using an AttachedProperty.
I cache every Trigger with an index for the final TriggerCollection. After the DataGridRow is rendered, else if (d is FrameworkElement frameworkElement) is true and the Style gets cloned with a new order of the Triggers.
public static class TriggerAttachedBehavior
{
private static readonly Dictionary<Trigger, int> _Triggers = new Dictionary<Trigger, int>();
/// <summary>
/// Gets property value.
/// </summary>
/// <param name="attachedObj"></param>
/// <returns></returns>
public static int GetOderIndex(Trigger attachedObj)
{
return (int)attachedObj.GetValue(OderIndexProperty);
}
/// <summary>
/// Sets property value.
/// </summary>
/// <param name="attachedObj"></param>
/// <param name="value"></param>
public static void SetOderIndex(Trigger attachedObj, int value)
{
attachedObj.SetValue(OderIndexProperty, value);
}
/// <summary>
/// The <see cref="OderIndexProperty"/> DependencyProperty.
/// </summary>
public static readonly DependencyProperty OderIndexProperty = DependencyProperty.RegisterAttached("OderIndex", typeof(int), typeof(TriggerAttachedBehavior), new UIPropertyMetadata(-1, OderIndexChangedCallback));
/// <summary>
/// Occurs when OderIndexProperty has changed.
/// </summary>
/// <param name="d">Dependency object.</param>
/// <param name="args">Event arguments.</param>
private static void OderIndexChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d is Trigger attachedObj)
{
_Triggers.Add(attachedObj, (int)args.NewValue);
}
else if (d is FrameworkElement frameworkElement)
{
// clone style with trigger lock
var newStyle = new Style(frameworkElement.Style.TargetType, frameworkElement.Style);
newStyle.Triggers.Clear();
// add all triggers except the base
foreach (TriggerBase triggerBase in frameworkElement.Style.Triggers)
{
if(_Triggers.Any(t => _Equals(t.Key, triggerBase)))
continue;
newStyle.Triggers.Add(triggerBase);
}
// add the base class triggers
foreach (int i in _Triggers.Values.OrderBy(t => t))
{
newStyle.Triggers.Add(_Triggers.First(t => t.Value == i).Key);
}
// apply new style
frameworkElement.Style = newStyle;
}
}
private static bool _Equals(TriggerBase x, TriggerBase y)
{
if (x.GetType() != y.GetType())
return false;
switch (x)
{
case DataTrigger dataTrigger:
return false;
case EventTrigger eventTrigger:
return false;
case MultiDataTrigger multiDataTrigger:
return false;
case MultiTrigger multiTrigger:
return false;
case Trigger trigger:
return trigger.Property.Name.Equals((y as Trigger).Property.Name);
}
return false;
}
}
<Style TargetType="DataGridRow" x:Key="DataGridRowSelectionStyle" BasedOn="{StaticResource DataGridRowDefaultStyle}">
<Setter Property="behaviors:TriggerAttachedBehavior.OderIndex" Value="0"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True" behaviors:TriggerAttachedBehavior.OderIndex="998">
<Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsMouseOver}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True" behaviors:TriggerAttachedBehavior.OderIndex="999">
<Setter Property="Background" Value="{extensions:Theme Key=DataGrid_Row_IsSelected}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
MultiDataTrigger can be used to paint rows based on their status value only when IsMouseOver is false and IsSelected is false. Additional conditions (when they are met) prevent MultiDataTriggers from overriding base Triggers:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowSelectionStyle}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.OK}"/>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="{extensions:Theme Key=DGLB_Green}"/>
</MultiDataTrigger>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CurrentStatus}" Value="{x:Static production1:ProcessDataEval.NG}"/>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="{extensions:Theme Key=DGLB_Red}"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>

Change Visibility of one Control when it is changed on another one

I got 3 Controls in my XAML.
If the Visibility from one of them changes to Visible, the others Visibility should change to Hidden. So that only one of them can be Visible at a time.
My Xaml
<Control x:Name="Unselected">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Selection, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Selected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
<Control x:Name="Selection" Visibility="Hidden">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Unselected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Selected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
<Control x:Name="Selected" Visibility="Hidden">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Selection, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Unselected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
Use 3 RadioButton and modify their Template and use their IsChecked property. Only one of them would be checked at a time. Bind your Visibility property to IsChecked property. If IsChecked is true, Visibility = Visible else Visibility = Hidden .
You can use Binding and PropertyChanged. Setting the property will raise PropertyChanged and Triggers will execute.
XAML:
<DockPanel LastChildFill="false">
<TextBox x:Name="Unselected" DockPanel.Dock="Top" Text="Unselected">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="Selection" DockPanel.Dock="Top" Text="Selection">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="Selected" DockPanel.Dock="Top" Text="Selected">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button Content="Unselected" DockPanel.Dock="Top" Click="Button_Click"></Button>
<Button Content="Selection" DockPanel.Dock="Top" Click="Button_Click_1"></Button>
<Button Content="Selected" DockPanel.Dock="Top" Click="Button_Click_2"></Button>
</DockPanel>
Code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Button_Click(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Unselected;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Selection;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Selected;
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private Options _enumOptions;
public Options EnumOptions
{
get { return _enumOptions; }
set
{
_enumOptions = value;
OnPropertyChanged("EnumOptions");
}
}
}
public enum Options
{
Unselected,
Selection,
Selected
}
Basically, you will set need to set property like this
EnumOptions = Options.Unselected;
instead of
Unselected.Visibility = true;

Using a DataTrigger in my Style

I am trying to change the fill of a rectangle based upon a value in my ViewModel but despite having tried all the suggestions I have found online, it still isn't working.
The IsMouseOver trigger works fine but the DataTrigger is ignored despite the fact there is always either a 4 or 5 in my ViewModel property.
Could somebody show me where I may be going wrong?
Thanks.
This is my style:
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="4">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="5">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
EDIT: So it seems that my Binding was incorrect due to my incorrect assumption that the DataContext of the UserControl is where I should direct the binding.
The Rectangle is in the template for a ListBox and has "Items" as it's DataContext so by changing the "Binding" it now works.
Many thanks for all assistance though:
SOLUTION:
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding DataContext.Theme,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl} }" Value="4">
<Setter Property="Rectangle.Fill"
Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.Theme,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl} }" Value="5">
<Setter Property="Rectangle.Fill"
Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Change your TargetType="Rectangle"
Here is the code I used to test
<Style x:Key="FavouriteRectangleStyle" TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Theme}" Value="4">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Theme}" Value="5">
<Setter Property="Fill" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
And the implementation
<Rectangle Style="{StaticResource FavouriteRectangleStyle}"/>
Inside the constructor of your window or usercontrol what you haveused, you should set this.DataContext = this; Otherwise binding not properly worked for the corresponding window or usercontrol.
That probably because you miss to implement the INotifyPropertyChanged interface, update you Theme property to this:
private int _theme = 5;
public int Theme
{
get
{
return _theme;
}
set
{
if (_theme == value)
{
return;
}
_theme = value;
OnPropertyChanged();
}
}
and make sure your codebehind or your viewmodel is implementing the interface
public partial class MainWindow : Window,INotifyPropertyChanged
{
here a full working sample:
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<SolidColorBrush x:Key="content__star__hex646464__shadow" Color="Red"/>
<SolidColorBrush x:Key="content__star__favorit__hexebebeb__shadow" Color="Green"/>
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="4">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="5">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Rectangle Style="{StaticResource FavouriteRectangleStyle}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Height="100"></Rectangle>
<Button Content="Change Theme" Click="ButtonBase_OnClick"></Button>
</StackPanel>
and the code behind :
public partial class MainWindow : Window,INotifyPropertyChanged
{
private int _theme = 5;
public int Theme
{
get
{
return _theme;
}
set
{
if (_theme == value)
{
return;
}
_theme = value;
OnPropertyChanged();
}
}
public MainWindow()
{
this.InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Theme = Theme == 5 ? 4 : 5;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Answer in EDIT part of original question. Thanks for all assistance

Setting ContentPresenter with MultiDataTrigger produces memory leak

I have a ListBox which Item's ContentPresenter is changed by MultiDataTrigger depending on some IsTool and IsAlerting boolean properties:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="LayerTemplate">
<ContentControl x:Name="contentControl" Style="{DynamicResource PageHeaderContentControlStyle}">
<layers:PageHeader/>
</ContentControl>
</DataTemplate>
<DataTemplate x:Key="ToolTemplate">
<ContentControl x:Name="contentControl" Style="{DynamicResource ToolHeaderContentControlStyle}">
<layers:ToolHeader/>
</ContentControl>
</DataTemplate>
<DataTemplate x:Key="LayerAlertTemplate">
<ContentControl x:Name="contentControl" Style="{DynamicResource PageHeaderAlertContentControlStyle}">
<layers:PageHeader/>
</ContentControl>
</DataTemplate>
<DataTemplate x:Key="ToolAlertTemplate">
<ContentControl x:Name="contentControl" Style="{DynamicResource ToolHeaderAlertContentControlStyle}">
<layers:ToolHeader/>
</ContentControl>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="EmptyListViewSelection">
<Setter Property="Background" Value="Transparent" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border BorderBrush="Transparent"
BorderThickness="0"
Background="{TemplateBinding Background}"
Margin="2"
FocusVisualStyle="{x:Null}">
<ContentPresenter DataContext="{Binding}" Name="contentPresenter"/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsTool}" Value="False">
<Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource LayerTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsTool}" Value="True">
<Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource ToolTemplate}"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsTool}" Value="False"/>
<Condition Binding="{Binding IsAlerting}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource LayerAlertTemplate}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsTool}" Value="True"/>
<Condition Binding="{Binding IsAlerting}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="contentPresenter" Property="ContentTemplate" Value="{StaticResource ToolAlertTemplate}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Name="listView"
BorderThickness="0"
ItemsSource="{Binding Pages}"
SelectedValue="{Binding SelectedPage}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ItemContainerStyle="{StaticResource EmptyListViewSelection}"
IsTabStop="False"
FocusVisualStyle="{x:Null}"
Focusable="False" Background="{x:Null}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" MaxWidth="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualWidth}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<GroupBox Name="pageBox" IsEnabled="{Binding Visible}" Grid.Row="1" DataContext="{Binding Path=SelectedValue, ElementName=listView}">
<GroupBox.Header>
<Label Content="{Binding Name}" Padding="0"/>
</GroupBox.Header>
<ContentControl Content="{Binding}"/>
</GroupBox>
</Grid>
If the notification of IsAlerting property change (via INotifyPropertyChange) is fired approximately one time per second, the memory usage of my application increases from 230MB up to 804MB for 15 minutes and the application finally crashes with the following call stack:
Exception Info: System.Reflection.TargetInvocationException
Stack:
at System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlReader, System.Xaml.XamlObjectWriter)
at System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlObjectWriter)
at System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(System.Windows.DependencyObject, System.Windows.Markup.IComponentConnector, System.Windows.Markup.IStyleConnector, System.Collections.Generic.List`1<System.Windows.DependencyObject>, System.Windows.UncommonField`1<System.Collections.Hashtable>)
at System.Windows.FrameworkTemplate.LoadContent(System.Windows.DependencyObject, System.Collections.Generic.List`1<System.Windows.DependencyObject>)
at System.Windows.StyleHelper.ApplyTemplateContent(System.Windows.UncommonField`1<System.Collections.Specialized.HybridDictionary[]>, System.Windows.DependencyObject, System.Windows.FrameworkElementFactory, Int32, System.Collections.Specialized.HybridDictionary, System.Windows.FrameworkTemplate)
at System.Windows.FrameworkTemplate.ApplyTemplateContent(System.Windows.UncommonField`1<System.Collections.Specialized.HybridDictionary[]>, System.Windows.FrameworkElement)
at System.Windows.FrameworkElement.ApplyTemplate()
at System.Windows.FrameworkElement.MeasureCore(System.Windows.Size)
at System.Windows.UIElement.Measure(System.Windows.Size)
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.ContextLayoutManager.UpdateLayoutCallback(System.Object)
at System.Windows.Media.MediaContext+InvokeOnRenderCallback.DoWork()
at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
at System.Windows.Media.MediaContext.RenderMessageHandlerCore(System.Object)
at System.Windows.Media.MediaContext.RenderMessageHandler(System.Object)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
.............
any ideas?
Not sure about the memory leak, but i wouldn't change the contenttemplate like this. If you set the ContentTemplate on a ContentPresenter, the ContentSource property of the ContentPresenter (which is 'Content' by default) will be overruled/ignored, the Content property will be empty, and your ContentTemplate doesn't have a datacontext anymore.
It's better to set the ContentTemplate in a style setter and use style triggers to change it. The default ContentSource of the ContentPresenter will do the rest.
I suspect this might solve your memory leak.
Your memory leak may be caused by the situation described in Can bindings create memory leaks in WPF?, perhaps exacerbated by virtualization in the list box (or maybe lack thereof if you have a lot of items and it is allocating them all at once). The fact that you have a System.Reflection.TargetInvocationException makes me think you are binding to something that isn't an INotifyPropertyChanged, which then has to use reflection to figure out if values are changing.
In newer versions of Visual Studio (2013, maybe 2012), there is a memory profiler that can show what objects are leaking which may help you narrow your search.
for some reason moving MultiDataTrigger to a separate style solved the problem (there is no memory leak anymore):
<Style TargetType="{x:Type ContentControl}" x:Key="PageToolHeaderContentControlThemeStyle">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Padding" Value="5,3"/>
....
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Alert}" Value="Warning"/>
<Condition Binding="{Binding IsAcknowledged}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="PART_BorderAlarmFrame" Property="Stroke" Value="Orange"/>
<Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Alert}" Value="Warning"/>
<Condition Binding="{Binding IsAcknowledged}" Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
</MultiDataTrigger.Setters>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="BackgroundBrush"
Storyboard.TargetProperty="Color"
From="Transparent" To="Orange" Duration="0:0:0.5"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Alert}" Value="Alarm"/>
<Condition Binding="{Binding IsAcknowledged}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="PART_BorderAlarmFrame" Property="Stroke" Value="Red"/>
<Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Alert}" Value="Alarm"/>
<Condition Binding="{Binding IsAcknowledged}" Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="PART_BorderAlarmFrame" Property="Visibility" Value="Visible"/>
</MultiDataTrigger.Setters>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="BackgroundBrush"
Storyboard.TargetProperty="Color"
From="Transparent" To="Red" Duration="0:0:0.5"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{DynamicResource LightForegroundBrush}"/>
<Setter TargetName="PART_BorderDefaultFrame" Property="Stroke" Value="{StaticResource ButtonPressedBackgroundBrush}"/>
<Setter TargetName="PART_Border" Property="Background" Value="{StaticResource ButtonHoverBackgroundBrush}"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource LightForegroundBrush}"/>
<Setter TargetName="PART_BorderDefaultFrame" Property="Stroke" Value="{StaticResource ButtonPressedBackgroundBrush}"/>
<Setter TargetName="PART_Border" Property="Background" Value="{StaticResource ButtonPressedBackgroundBrush}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF ControlTemplate TargetType match

I have a component that is derived from a ComboBox. In it, I created a dependency property "myDependencyProp" as the following code. However, in XAML, when I refer to this property, VS says the type is not available in ComboBox and don't run the application. When I set the ControlTemplate TargetType to my:myComboBox, it works correctly, however, VS displays a warning: "myComboBox ControlTemplate TargetType does not match templated type ComboBox". The application runs correctly, but I don't like this warning. How can I fix it? Thanks! (Obs: my: is just a ficticious alias to myNameSpaces).
C#:
namespace myNameSpace
{
public partial class myComboBox : ComboBox
{
public static readonly DependencyProperty myDependencyPropProperty =
DependencyProperty.Register("myDependencyProp", typeof(bool),
typeof(myComboBox), new UIPropertyMetadata(false));
public myComboBox()
{
InitializeComponent();
}
public bool IsFirstItemTip
{
get { return (bool)GetValue(myDependencyPropProperty); }
set { SetValue(myDependencyPropProperty, value); }
}
}
}
XAML:
<ComboBox.Resources>
<Style x:Key="myComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:myComboBox}">
<Grid>
<ToggleButton
...
</ToggleButton>
<ContentPresenter
...
</ContentPresenter>
<Popup
...
</Popup>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="SelectedIndex" Value="0"/>
<Condition Property="myDependencyProp" Value="True"/>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter ... />
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Resources>
change this line
<Style x:Key="myComboBox" TargetType="{x:Type ComboBox}"> to <Style x:Key="myComboBox" TargetType="{x:Type my:myComboBox}"> to get rid of the warning. The warning is telling you that your style will implicitly get applied to all ComboBoxes but the control template is intended for those types which are derived from myComboBox
Thanks for the answers, but I solved the problem by doing the following:
First, I returned all TargetTypes to {x: Type ComboBox}, then all warnings are gone.
After, instead of using the MultiTrigger as previously presented, I used MultiDataTrigger, as the code below:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=SelectedIndex, RelativeSource={RelativeSource Self}}" Value="0"/>
<Condition Binding="{Binding Path=myDependencyProp, RelativeSource={RelativeSource Self}}" Value="True"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter .../>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
That is it!

Resources