Binding a WPF Style Trigger to a custom dependency property - wpf

I have found numerous similar threads here, but none that seem to address my specific issue.
I need to highlight the background of a textbox under certain conditions. I have created a Highlight property and tried using a trigger in a style to set it but it doesn't actually ever highlight the text.
Here is my Style, simplified:
<Style x:Key="TextBoxStyle" BasedOn="{StaticResource CommonStyles}">
<Style.Triggers>
<Trigger Property="Elements:DataElement.Highlight" Value="True">
<Setter Property="Control.Background"
Value="{DynamicResource EntryBoxHighlightBackground}"/>
</Trigger>
</Style.Triggers>
</Style>
Elements is defined as:
xmlns:Elements="clr-namespace:MDTCommon.Controls.Forms.Elements">
Then I have the section where the style is applied:
<!-- Applies above style to all TextBoxes -->
<Style TargetType="TextBox" BasedOn="{StaticResource TextBoxContentHolder}" >
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
<!-- Overrides the default Error Style -->
</Style>
In the code behind of the DataElement class is the following:
public static readonly DependencyProperty HighlightProperty =
DependencyProperty.Register("Highlight", typeof(bool), typeof(DataElement));
public bool Highlight
{
get { return (bool)base.GetValue(HighlightProperty); }
set { base.SetValue(HighlightProperty, value); }
}
A DataElement ultimately derived from UserControl and it contains a reference to TextBox object as well as othe objects.
In the CustomForm class that houses all of the DataElement objects I have the following to set the color.
Resources["EntryBoxHighlightBackground"] = Brushes.Yellow;
So, the first issue is that setting the Highlight property for the DataElement doesn't cause the textbox background to draw in yellow.
The other issue is that I realize that I am applying this style to all textboxes and I could have textboxes in other areas that are not actually contained within a DataElement, which may cause a binding issue.

Try converting your trigger to a DataTrigger, and add a binding that will look directly at the DataElement control, like so:
<DataTrigger Binding="{Binding Path=Highlight, RelativeSource={RelativeSource AncestorType={x:Type Elements:DataElement}}}" Value="True">
<Setter Property="Control.Background" Value="{DynamicResource EntryBoxHighlightBackground}"/>
</DataTrigger>

Related

how could I set the cell style in a column in a datagrid dynamically with converter or datatrigger?

I would like to set dynamically the style of a row in a column of a datagrid according to a data in the data row, so according to the value of that property, only one property, I would like to choose between 3 different styles that I have in a resource dictionary.
I am trying this solution:
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource ResourceKey=DataGridCellLeftHorizontalAlignment}">
<Style.Triggers>
<DataTrigger Binding="{Binding NumeroLineaFactura}" Value="-1">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
But this has the problem that I can only select the one of the styles and I have to change the rest of the properties one by one, so how I have to set the style in various columns, it makes me to repeat the code.
I would like to can select the style with a data trigger or with a converter, but I don't know the syntax to can do it, because in the trigger I can set the property Style neither CellStryle.
How could I set the cell style dynamically?
Thanks.
Since there is no CellStyleSelector property available and you cannot use a DataTrigger to change the Style itself, you should use a single Style and then use several DataTriggers to change the properties of the DataGridCell, e.g.:
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding NumeroLineaFactura}" Value="-1">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextAlignment" Value="Right"/>
</DataTrigger>
</Style.Triggers>
</Style>
Thanks. In this case it works, but if I have another column that for the -1 value has the same style, I have to repeat the code...
Right. In this case you could consider implementing your own custom DataGridTextColumn and programmatically set the CellStyle. Something like this:
public class CustomDataGridTextColumn : DataGridTextColumn
{
public static readonly DependencyProperty FirstStyleProperty = DependencyProperty.Register(
name: nameof(FirstStyle),
propertyType: typeof(Style),
ownerType: typeof(CustomDataGridTextColumn)));
public Style FirstStyle
{
get => (Style)GetValue(FirstStyleProperty);
set => SetValue(FirstStyleProperty, value);
}
//...
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
FrameworkElement element = base.GenerateElement(cell, dataItem);
var val = dataItem as YourClass;
if(val != null)
{
switch (val.NumeroLineaFactura)
{
case -1:
cell.Style = FirstStyle;
break;
//...
}
}
return element;
}
}
XAML::
<DataGrid.Columns>
<local:CustomDataGridTextColumn ... FirstStyle="{StaticResource yourFirstStyle}" />
</DataGrid.Columns>

Bind to default property value of another control style definition in xaml

I have a CustomControl and want to have the default value of TextBox default style definition. I don't know if it that is even possible.
This is what I have tried so far without success. But here you can see what I have in mind.
My default implementation to change the default style of the TextBox (in for example app.xaml to apply it global)
<Style TargetType="TextBox">
<Setter Property="BorderThickness" Value="2"/>
</Style>
Here I want to get the value of '2'.
<Style TargetType="controls:CustomControl">
<Setter Property="BorderThickness" Value="{Binding Source={x:Static TextBox.BorderThicknessProperty}}"/>
</Style>
The default value of the BorderThickness property of a TextBox is defined in the default style of the TextBox which is eventually applied to an instance of a TextBox at runtime.
So you cannot do something like this:
<Style TargetType="controls:CustomControl">
<Setter Property="BorderThickness" Value="{Binding Source={x:Static TextBox.BorderThicknessProperty}}"/>
</Style>
...unless you bind to an actual instance of a TextBox that uses the default style.
You could look at the default template of the TextBox and simply copy its default BorderThickness property value of 1 though:
<Style TargetType="controls:CustomControl">
<Setter Property="BorderThickness" Value="1"/>
</Style>
Obviously you can also bind to a property of a class from more than one Style, e.g.:
<Style TargetType="controls:CustomControl">
<Setter Property="BorderThickness" Value="{Binding Thickness, Source={StaticResource settings}}"/>
</Style>

Can not change template of ContentControl from style's datatrigger - datatrigger not firing

Hello WPF aficionados,
I have a content control inside a user control that sets it data context to the selected item of a listview inside the same usercontrol. The selected item property on the view model is called Selection.
Here is the content control's declaration:
<ContentControl DataContext="{Binding Selection}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource JobSnapShotProgDetail}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding bTandM}" Value="1">
<Setter Property="Template" Value="{StaticResource JobSnapShotProgDetail}"/>
</DataTrigger>
<DataTrigger Binding="{Binding bTandM}" Value="0">
<Setter Property="Template" Value="{StaticResource JobSnapShotTM_Detail}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The Templates are defined as ControlTemplates in another resource file like:
<ControlTemplate x:Key="JobSnapShotProgDetail" >
...
</ControlTemplate >
<ControlTemplate x:Key="JobSnapShotTM_Detail" >
...
</ControlTemplate >
The binding works great meaning that the template's fields all display the correct data from the Selection object.
My Problem:
I want the content control to use one of two different control templates based on the value of the Selection's bTandM property.
What happens now:
When I change the selection of the listview, which changes the Selection object, the template IS NOT being changed based on the value of the Selection's bTandM property (a sql server bit data type). Yes the Selection object implements iNotifyPropertyChanged, and the ControlTemplate's fields that bind to the Selection's properties all display the correct data without any binding errors in the output window.
What I Tried:
It seems like the datatrigger is not getting "HIT" by the code. I tried to break the datatrigger by adding foo instead of a number which should have not been able to convert, but no error is generated. ie:
<DataTrigger Binding="{Binding bTandM}" Value="foo">
This leads me to believe that the trigger is not firing for some reason.
Question:
Can someone help me figure out why this data trigger has no effect.
Thanks in advance
JK
EDIT 1:
I tried to use a technique found HERE, but it also does not work. THe data triggers are not getting fired.
New attempt using DataTemplate with DataTriggers that target a contentcontrol's template property:
<ItemsControl ItemsSource="{Binding SelectionCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="DetailControl" Template="{DynamicResource JobSnapShotProgDetail}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding bTandM}" Value="False">
<Setter TargetName="DetailControl" Property="Template" Value="{DynamicResource JobSnapShotProgDetail}" />
</DataTrigger>
<DataTrigger Binding="{Binding bTandM}" Value="1">
<Setter TargetName="DetailControl" Property="Template" Value="{DynamicResource JobSnapShotTM_Detail}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Again, the template loads fine and the properties are display correctly for the initial template set in the style setter, but the templates do not change based on the object's bTandM boolean property.
Okay, so this turned out to be an issue with NotifyPropertyChanged.
The selection property of my viewmodel does implement INPC but the problem was that the underlying class type (vw_Job_Snapshot which is a mapped entity from a sql server view) of selection did not.
I had to implement iNPC in the underlying class and then in my viewmodel use the Property Setter for the Selection Property to also notify the specific bTandM property change like so:
Public Property Selection As vw_Job_Snapshot
Get
Return Me._Selection
End Get
Set(ByVal value As vw_Job_Snapshot)
Me._Selection = value
''Notify of specific property changed
value.OnPropertyChanged("bTandM")
_Selection = value
RaisePropertyChanged(JobSnapshotSelectedPropertyName)
End Set
End Property
After this the following code in my view worked perfectly:
<ContentControl DataContext="{Binding Selection}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{DynamicResource JobSnapShotTM_Detail}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding bTandM}" Value="False">
<Setter Property="Template" Value="{DynamicResource JobSnapShotProgDetail}"/>
</DataTrigger>
<DataTrigger Binding="{Binding bTandM}" Value="True">
<Setter Property="Template" Value="{DynamicResource JobSnapShotTM_Detail}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Thanks to everyone who helped
Try using a DataTemplateSelector instead.
Your selector would look something like this (I'm assuming your class is called JobSnapShot):
public class JobSnapShotDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is JobSnapShot)
{
JobSnapShot jobSnapShot = item as JobSnapShot;
if (jobSnapShot.bTandM == 1)
return
element.FindResource("JobSnapShotProgDetail") as DataTemplate;
else
return
element.FindResource("JobSnapShotTM_Detail") as DataTemplate;
}
return null;
}
}
XAML:
<Window.Resources>
<local:JobSnapShotDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
<ContentControl ItemTemplateSelector="{StaticResource myDataTemplateSelector}" .... />

Specify a binding in an application-level style resource?

I'm fairly new to WPF and have created a style to alter the appearance of a button control. The style contains a data trigger to change the button background (amongst other things) based on a boolean property in the data context, e.g.:-
<Style x:Key="IndicatorButton" TargetType="Button">
<DataTrigger Binding="{Binding Path=ValveIsOpen}" Value="True">
<Setter Property="Background" Value="#00FF00"/>
..etc..
Currently the style is only used by a single button, so the data trigger binding is hard-coded with a property called "ValveIsOpen".
I now want to re-use this style throughout my app, with different buttons being bound to different properties. How would I change the data trigger binding on each button that the style is applied to?
Many thanks
You need to define a base style and derived styles, such as
<Style x:Key="IndicatorButton" TargetType="Button">
<Setter Property="Foreground" .../>
...
<Style x:Key="ValveIndicatorButton" TargetType="Button" BasedOn={StaticResource IndicatorButton}>
<DataTrigger Binding="{Binding Path=ValveIsOpen}" Value="True">
<Setter Property="Background" Value="#00FF00"/>
..etc..

How do I write a datatrigger to change the value of a dependency property based on a change of a second dependency property?

I want to change the state of a dependency property (Mode) in a UserControl triggered off of a change change of a second dependency property (ViewType).
I tried defining a DataTrigger as follows, but for some reason it doesn't work:
<UserControl.Resources>
<Style TargetType="{x:Type UserControls:MyUserControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="ViewType2">
<Setter Property="Mode" Value="Logon"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
To verify that my binding was working correctly I wrote the following test XAML in the same user control:
<Style x:Key="butstyle">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="ViewType2">
<Setter Property="Control.Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
When I assigned this style to a button, I see the foreground color change to white at the right time, which proves my Binding to ViewType is working.
I seems I need to use a style under usercontrol.resources (like in the first block of code above), but the "Mode" property is never set.
Is this the right way to do this? I'd like to try to keep this in XAML if possible, but I'm not comfortable how triggers should work when they are not related directly to visual elements.
In case its relevant, the "ViewType" bound property is defined in a parent UserControl, and the "Mode" property is defined on MyUserControl.
Update:
I used a converter to find out that my trigger is being called, however my setter is not getting engaged for some reason. Here is the code for my DP which I am trying to set. Breakpoints on neitehr the set() nor the OnModeChanged() are called. Is there anything wrong with my Dependency Property?
public enum ModeStates { Logon, NextState }
public ModeStates Mode
{
get { return (ModeStates)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
protected static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
"Mode", typeof(ModeStates), typeof(MyUserControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnModeChanged))
);
private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// .......
}
Update:
I was able to use DependencyPropertyDescriptor.AddValueChanged() to solve my problem in a pretty clean fashion.
If the dependency properties in question are defined on the UserControl you need to set it's style property, not add a default style inside the resources.
to set the style inline in XAML
<UserControl.Style>
<Style TargetType="{x:Type UserControls:MyUserControl}">
<!-- Triggers -- >
</Style>
</UserControl.Style>
In addition to Adam's answer, are you setting the Mode property in the <UserControl> tag anywhere? If you do, it is overwriting the triggered value.
Also, on occasion I have had issues with a triggered value not getting set unless a default value is also defined in the style.
For example, sometimes this will not work
<UserControl.Style>
<Style TargetType="{x:Type UserControls:MyUserControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="ViewType2">
<Setter Property="Mode" Value="Logon"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
while this does
<UserControl.Style>
<Style TargetType="{x:Type UserControls:MyUserControl}">
<Setter Property="Mode" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="ViewType2">
<Setter Property="Mode" Value="Logon"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>

Resources