Create A Custom Event That Can Be Triggered From Code Behind - c#

I'm breaking my pick on something I would have thought to be pretty simple. We have an application which uses some advanced color animations on a panel or a border control to attract the users attention when specific conditions occur. Usually those conditions are identified by communicating with an external device and could occur at any time. So I want to be able to define a trigger for a border or stackpanel like I might for it's standard IsEnabled or IsFocused properties but one that I can trigger arbitrarily form the Main Window's code-behind (IsMyArbitraryCondition). I imagine it would look like this:
<Border x:Name="Fancy_Border">
<Border.Style>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMyArbitraryCondition" Value="true">
<Trigger.EnterActions>
<BeginStoryboard x:Name="Board" Storyboard="{StaticResource ComplicatedAnimation}"/>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
Then in the code-behind for the main window:
Fancy_Border.IsMyArbitraryCondition = true;
// ComplicatedAnimation occurs
I've seen attached properties, dependency properties, etc suggested but my level of knowledge about how to us WPF has been insufficient so far to use those examples successfully. It seems like there must be a relatively simple way that I can still define my complex trigger behavior with the convenience of an XAML style but have that style trigger fired by something (anything!) other than the built in properties (IsMouseOver, IsPressed, IsVisible, etc etc).
EDIT (Using UserControl And DataTrigger)
I have a complete and working example provided below. The only remaining issue is that I can't put named components inside the content of my extended border control. The compiler returns:
Cannot set Name attribute value 'Example' on element 'TextBlock'. 'TextBlock' is under the scope of element 'HighlightBorder', which already had a name registered when it was defined in another scope.
This is an issue as I'm not developing using MVVM and seek to directly manipulated some individual controls that are within the HighlightBorder. Any workarounds you know of for that would be very helpful!
HighlightBorder.cs
using System;
using System.Windows;
using System.Windows.Controls;
namespace App
{
public partial class HighlightBorder : Border
{
public HighlightBorder()
{
InitializeComponent();
}
public bool IsActiveHighlight
{
get { return (bool) GetValue(ActiveHighlightProperty); }
set { SetValue(ActiveHighlightProperty, value); }
}
public static readonly DependencyProperty ActiveHighlightProperty =
DependencyProperty.Register("IsActiveHighlight", typeof(bool), typeof(HighlightBorder), new UIPropertyMetadata(null));
}
}
HighlightBorder.xaml
<Border x:Class="App.HighlightBorder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border.Style>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActiveHighlight, RelativeSource={RelativeSource Mode=Self}}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="EnabledPulse" Storyboard="{DynamicResource PulseAccent}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="EnabledPulse"/>
<BeginStoryboard x:Name="EnabledClear" Storyboard="{DynamicResource ClearAccent}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
MainWindow.xaml
<local:HighlightBorder x:Name="Grouped_Controls">
<TextBlock> Example Content </TextBlock>
</local:HighlightBorder>
MainWindow.cs
Grouped_Controls.IsActiveHighlight = false; // Or true

Related

Binding int variable as value in style

I am trying to find how to bind an int variable as my value in style. Here is my simple xaml style.
<Style TargetType="Button" x:Key="ButtonMenu">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="White"/>
</Style>
In the fourth row, I wanna change the value from 14 to my variable called SizeVar bud I can't find how to do it. I really tried googling but can't find anything that works for me. I know that I should do it somehow through binding but not exactly how.
I tried using different binding options but couldn't find one that works for me. My English is also not perfect so maybe it is possible that I misunderstood something.
From your description I interpreted the following:
You have a XAML-file where in the Resources you want to contain a style called "ButtonMenu".
The variable that binds to the FontSize is called "SizeVar" and is contained in the DataContext, which is set.
With this, I tested it out and the following works.
In the View (XAML):
...
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="Button" x:Key="ButtonMenu">
<Setter Property="FontSize" Value="{Binding SizeVar}"/>
</Style>
</Window.Resources>
<Grid>
<Button
Content="Test"
Style="{DynamicResource ButtonMenu}"/>
</Grid>
...
In the associated ViewModel (C#):
private int _sizeVar;
public int SizeVar
{
get => _sizeVar;
set
{
_sizeVar = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
SizeVar = 14;
}

WPF control not returning in previous state after triggering

XAML
<utility:InvalidNotification x:Name="InvalidNotificationControl"/>
<Button Content="Clean AppV Cache" Click="Button_Click">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=InvalidNotificationControl, Path=Visibility}" Value="Visible">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
NOTE
InvalidNotification is a custom UserControl
Now, the DataTrigger works fine initially and disable the button since the Usercontrol is visible.
The problem is when I collapse the Usercontrol based on another condition the button stays disable. I found this related answer which states that The properties changed by triggers are automatically reset to their previous value when the triggered condition is no longer satisfied. which is not my case. Why is that ?
EDIT
Thanks to #mm8 which led me to the solution. So if ever you're trying to bind a control on a UserControl's content (inner TextBlock in my case), just add a second trigger at the bottom of your Usercontrol like so,
<UserControl.Style>
<Style TargetType="UserControl">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Notification, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
where notification would be the name of my TextBlock
Your example works provided that you toggle/set the Visibility property of the InvalidNotification control itself, since it is this property that you bind to.
If you set the Visibility property of some element within the InvalidNotification control, you need to bind to this specific element.
You can't do this using an ElementName binding though because the Button and any element defined in the InvalidNotification control don't belong to the same namescope.

C# WPF Xaml: Globally set all text in a view to one color, and all backgrounds to another

Is there a way (using data binding or simply xaml) to set the background of all elements in a view to one color, and all text to another?
I know I can edit each element in the view one by one, but I I'd like to see if this is possible with settings at a global level. Kind of like how everything by default is set to black on white.
I guess what I'm asking is if there is a feature/setting of a WPF application that offers what I'm looking for, and/or what I should search to find an answer online.
My project isn't using anything but what visual studio offers when you create a WPF project, so I can't use a prism or mvvm light approach.
Thanks in advance for your answer!
Globally...or simply XAML...
if there is a feature/setting of a WPF application that offers what I'm looking for
In Application Resource add style like this:
<Style TargetType="Control">
<Setter Property="Background" Value ="Blue"/>
<Setter Property="Foreground" Value ="Red"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="Background" Value ="Blue"/>
<Setter Property="Foreground" Value ="Red"/>
</Style>
Based on your target element you want to set background.
When you say "the background of all elements in a view" you should be more specific, If by 'element' you mean UIElement then there is no Background property in UIElement. If it means Control then not all UIElementsderive from Control (e.g. TextBlock) and finally if it means every UIElement derived type defined in your view that have a Background property, then you should add different styles for each type without setting the x:key to the YourView.Resources like this:
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="Blue" />
</Style>
...
</Window.Resources>
uses controls collection through which you can control all controls in WPF

How to make a WPF resource be recalculated when a trigger's run?

In short: I've got a Style. It uses TemplateBinding a fair bit to make it parametrized instead of repeating myself over and over again. However, when a trigger for that style gets used and a resource gets used in a setter in that trigger, it just doesn't show up! Not even the default value gets shown. Here's a small program that replicates this issue:
TestDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label Content="{TemplateBinding lcl:TestClass.String}" x:Key="innerLabel"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
Title="Test" Width="500" Height="350">
<Window.Resources>
<ResourceDictionary Source="TestDictionary.xaml"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="Enable/Disable" Click="Click"/>
<Button Grid.Column="1" x:Name="btn" Style="{DynamicResource BtnTest}" lcl:TestClass.String="TESTING"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Click(object sender, RoutedEventArgs e)
{
btn.IsEnabled = !btn.IsEnabled;
}
}
public class TestClass
{
public static string GetString(DependencyObject obj)
{
return (string)obj.GetValue(StringProperty);
}
public static void SetString(DependencyObject obj, string value)
{
obj.SetValue(StringProperty, value);
}
public static readonly DependencyProperty StringProperty =
DependencyProperty.RegisterAttached("String", typeof(string), typeof(TestClass), new PropertyMetadata("Default!"));
}
}
Instead of using a TemplateBinding, I also tried this:
{Binding Path=lcl:TestClass.String, RelativeSource={RelativeSource AncestorType={x:Type Button}}}
It still didn't work.
I know I'm probably doing something wrong, but the question is: what is it?
All you really need to make this work is to use RelativeSource in your binding. Since you are setting the attached property on the Button, in your style trigger, you can just bind to the attached property on self:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content"
Value="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
One cool thing about using your approach, since Button is a ContentControl, you're attached property can be any object, not just strings.
And to clarify what went wrong in your previous approach -
As others have said, TemplateBinding only works in ControlTemplates. It also only works when the DependencyProperty is defined on the class you are creating the template for (so you can never do a TemplateBinding to Grid.Row for example)
When binding to an attached property, the whole thing needs to be in parentheses, otherwise WPF will try to bind to a property of a property. Otherwise your RelativeSource binding was close!
I think if you want to have a Label inside the Button as the content, it may work (I didn't test that), but it doesn't seem like the best idea, as your Button can host any object you want.
EDIT for more complex example
So, if you need to display more than one dynamic property, I would recommend using a DataTemplate:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Content="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Also, I want to point out that a DataTemplateSelector might be more applicable if you have multiple different criteria for changing the look of the content.
Now I see the details. What you should write before relative source is like:
Binding Path=(lcl:TestClass.String)
Do not forget to add parenthesis.
Your example does not work because TemplateBinding only works in a ControlTemplate. To achieve something akin to a TemplateBinding in Resources you need to do other stuff. Here's an example.
In order for TemplateBinding to work, you need to fix the code a little bit (this is just an example with no resources):
<Style x:Key="BtnTest" TargetType="{x:Type Button}">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="200" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Background="{TemplateBinding Background}">
<ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding lcl:TestClass.String}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
Useful links about this topic: Here, and here too.
EDIT:
You can also use the application settings instead of TestClass. Open "Project -> Properties: MyNamespace... -> Settings" and add your settings:
Name--------Type--------Scope--------Value
LabelText---string--------User----------Default
Set the your value for the LabelText in code. For example:
public MainWindow()
{
InitializeComponent();
MyNamespace.Properties.Settings.Default.LabelText = "Testing";
}
And use this ResourceDictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:MyNamespace.Properties"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label x:Key="innerLabel" Content="{Binding Source={x:Static properties:Settings.Default}, Path=LabelText, Mode=TwoWay}" />
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>

DynamicResource throws an exception

I have to apply the following style to my ListViewItem:
<UserControl.Resources>
<local:Look x:Key="ListViewItemLook" Background="Magenta"/>
<Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{Binding Source={DynamicResource ListViewItemLook}, Path=Background}"/>
</Trigger>
</Style.Triggers>
</Style>
But i get an exception, i try to change:
<Setter Property="Background" Value="{Binding Path=Background}"/>
And add to the Style:
<Setter Property="DataContext" Value="{DynamicResource ListViewItemLook}"/>
But is does not work. I can't bind to a StaticResource because I need to set the BackGround property run-time.
What have I to do? Thanks.
If you want both local:Look and the setter to refer to the same color, perform a small refactor:
Pull the color out into a separate SolidColorBrush and make both items refer to it:
<SolidColorBrush x:Key="SelectedListViewItemBackground" Color="Magenta" />
<local:Look x:Key="whatever" Background="{StaticResource SelectedListViewItemBackground}" />
<Setter Property="Background" Value="{StaticResource SelectedListViewItemBackground}" />
If you're trying to do something else, I can't figure out what it is because the question doesn't make sense.
As far as I am aware, DynamicResource extension uses the DependencyProperty mechanism (pretty much like a binding). Therefore you cannot set Source property of a Binding object with DynamicResource because it is not a DependencyProperty.
In addition, if you want to change the Background property of Look but not the Look itself in resources; then setting Look as a static resource to the binding property should not be a problem. Of course Background property of Look class should either trigger a PropertyChanged event or be a DependencyProperty itself.

Categories

Resources