I'm trying to change an image in a user control when a DependancyProperty on that user control is changed. As an example, I have the DependancyProperty StatusIndicator as a boolean. When it is true, I want to show the StatusOK image, and when its false I want to show the StatusBad image.
The data trigger seems to set the style of the image fine when the application first loads, since the StatusIndicator is false it sets the source of the image to StatusBad from StatusDisabled.
The problem is when I change the StatusIndicator value, the DataTriggers don't seem to notice. I monitored the change using WpfInspector, and it would always think that the StatusIndicator is false.
Below is the XAML.
<UserControl x:Class="WpfApplication6.StatusControl"
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="335" d:DesignWidth="300" x:Name="ThisUserControl">
<UserControl.Resources>
<BitmapImage x:Key="StatusDisabled" UriSource="/WpfApplication6;component/Images/status_light_gray.png" />
<BitmapImage x:Key="StatusBad" UriSource="/WpfApplication6;component/Images/status_red.png" />
<BitmapImage x:Key="StatusOK" UriSource="/WpfApplication6;component/Images/status_light_green.png" />
<Style TargetType="{x:Type Image}" x:Key="StatusImage">
<Setter Property="Source" Value="{StaticResource StatusDisabled}" />
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality" />
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=ThisUserControl, Path=StatusIndicator}" Value="False">
<Setter Property="Source" Value="{StaticResource StatusBad}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ThisUserControl, Path=StatusIndicator}" Value="True">
<Setter Property="Source" Value="{StaticResource StatusOK}" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Style="{StaticResource StatusImage}" Grid.Row="0" />
</Grid>
</UserControl>
And the code-behind.
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication6
{
/// <summary>
/// Interaction logic for StatusControl.xaml
/// </summary>
public partial class StatusControl : UserControl
{
public StatusControl()
{
InitializeComponent();
}
public static readonly DependencyProperty StatusIndicatorProperty = DependencyProperty.RegisterAttached(
"StatusIndicator", typeof(bool), typeof(bool), new PropertyMetadata(false));
public bool StatusIndicator
{
get { return (bool)this.GetValue(StatusIndicatorProperty); }
set { this.SetValue(StatusIndicatorProperty, value); }
}
}
}
Any help would be greatly appreciated.
Thank you!
The second type should be your owner Type StatusControl.
public static readonly DependencyProperty StatusIndicatorProperty = DependencyProperty.RegisterAttached(
"StatusIndicator", typeof(bool), typeof(StatusControl), new PropertyMetadata(false));
Hope this helps!
Related
I'm using reflection and expression trees in C# to build a fairly adaptable search tool for our database. Because of this, I have a need for a custom ContentControl - termed 'MultiStyleInputBox' - which uses data triggers to adjust its ContentTemplate to the Type of input expected. The problem is, while the code builds just fine and I have confirmed that both the public and static constructors are being hit when the code executes, the ContentControl's content doesn't show up at all in my UI.
Now, I'm relatively new to writing custom XAML/C# UI control classes, but I have been able to cobble together the following:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
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"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Resources>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Resources>
</ContentControl>
And the code behind:
public sealed partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
I thought at one point that I might not have set the content of the ContentControl, and so I added a <ContentPresenter/>, but I received an error saying that the content is set more than once, so I believe that my <Style.Setters></Style.Setters> section is taking care of that. Otherwise, even running around using PresentationTraceSources.TraceLevel="High" on my bindings, I haven't so far been able to run into any useful errors.
Is there some sort of glaring issue in my code that I can immediately address (hopefully)? Do I need to reevaluate my approach to the problem?
Update
After suggested corrections in the answers below, here is the latest version of the code:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
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"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox, Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
And the code-behind:
public partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
Here is a test instantiation of the MultiStyleInputBox (I'm using Mahapps.Metro):
<Controls:MetroWindow
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
x:Class="MyApp.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp"
Title="Test Window" Height="450" Width="800"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<StackPanel>
<local:MultiStyleInputBox x:Name="TestMultiBox" Value="1" InputType="{x:Type sys:Int32}"/>
</StackPanel>
</Controls:MetroWindow>
When I try to instantiate this class, I'm still not getting anything showing up in my UI, and the ContentControl isn't taking up any space. Even if I include Width="50" Height="24", I still get nothing. I've tested setting both Value and InputType in code-behind and using a breakpoint to inspect the object, and I'm finding that, while both values get set, the Content of the ContentControl remains null.
The immediate problem is that your style isn't applied to the ContentControl you want to apply it to. You're defining an implicit ContentControl style which will be applied to any ContentControls you create in the content of this control -- but you aren't creating any, and that's not what you want anyhow.
For a quick fix, just change ContentControl.Resources to ContentControl.Style.
<ContentControl x:Class="MyApp.MultiStyleInputBox"
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"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
Your next problem will be that selecting a new DateTime in the DatePicker won't update a property bound to the Value property of your viewmodel. Here's the fix for that:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
And the last problem (or the first, perhaps) was that you weren't calling InitializeComponent() in the constructor, which is always required in any WPF codebehind class:
public MultiStyleInputBox()
{
InitializeComponent();
}
I am trying to create a few WPF UserControls to include in a library to share with my team but there is something wrong with how Read-Only properties are working for me.
For this question I made a very simple user control with two DependencyProperties. One that is based on an enum and the other that performs an action based on the selected enum. The enum is being used to choose a style the button will use.
The application is a regular Wpf Application with a Wpf User Control Library as a reference. I have a suspicion that the Control Library might be contributing to the problem so I felt it was relevant to the example.
Wpf Control Library1
Dictionary1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfControlLibrary1">
<Style x:Key="SampleStyle-Font1">
<Setter Property="TextElement.FontFamily" Value="Wingdings" />
<Setter Property="TextElement.FontSize" Value="30" />
</Style>
<Style x:Key="SampleStyle-Font2">
<Setter Property="TextElement.FontFamily" Value="Elephant" />
<Setter Property="TextElement.FontSize" Value="30" />
</Style>
<Style x:Key="SampleStyle-Font3">
<Setter Property="TextElement.FontFamily" Value="Times New Roman" />
<Setter Property="TextElement.FontSize" Value="30" />
</Style>
</ResourceDictionary>
UserControl1.xaml:
<UserControl x:Class="WpfControlLibrary1.UserControl1"
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"
xmlns:local="clr-namespace:WpfControlLibrary1"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="200">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=Command}">
<StackPanel>
<Label Style="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=ReadOnlyStyle}" Content="{Binding Path=Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></Label>
</StackPanel>
</Button>
</ControlTemplate>
</UserControl.Template>
</UserControl>
UserControl1.xaml.cs
namespace WpfControlLibrary1 {
using System.Windows;
using System.Windows.Controls;
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl {
public enum StyleSelector {
Style1,
Style2,
Style3
}
public static DependencyProperty SelectedStyleProperty =
DependencyProperty.Register("SelectedStyle", typeof(StyleSelector), typeof(UserControl1), new PropertyMetadata(ReadOnlyStyle_Changed));
private static readonly DependencyPropertyKey ReadOnlyStylePropertyKey =
DependencyProperty.RegisterReadOnly("ReadOnlyStyle", typeof(Style),
typeof(UserControl1), null);
public UserControl1() {
InitializeComponent();
}
public StyleSelector SelectedStyle {
get => (StyleSelector)GetValue(SelectedStyleProperty);
set => SetValue(SelectedStyleProperty, value);
}
public Style ReadOnlyStyle => (Style)GetValue(ReadOnlyStylePropertyKey.DependencyProperty);
private static void ReadOnlyStyle_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (!(d is UserControl1 userControl1)) {
return;
}
Style style;
switch (userControl1.SelectedStyle) {
case StyleSelector.Style1:
style = (Style)userControl1.FindResource("SampleStyle-Font1");
break;
case StyleSelector.Style2:
style = (Style)userControl1.FindResource("SampleStyle-Font2");
break;
case StyleSelector.Style3:
style = (Style)userControl1.FindResource("SampleStyle-Font3");
break;
default:
style = (Style)userControl1.FindResource("SampleStyle-Font1");
break;
}
userControl1.SetValue(ReadOnlyStylePropertyKey, style);
}
}
}
Wpf Application
MainWindow.xaml:
<Window x:Class="ReadOnlyDependencyPropertiesWithUserControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ReadOnlyDependencyPropertiesWithUserControls"
xmlns:wpfControlLibrary1="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
mc:Ignorable="d"
Title="Example" Height="200" Width="400">
<StackPanel>
<wpfControlLibrary1:UserControl1 SelectedStyle="Style1" Content="This is the first control"></wpfControlLibrary1:UserControl1>
<wpfControlLibrary1:UserControl1 SelectedStyle="Style2" Content="This is the second control"></wpfControlLibrary1:UserControl1>
<wpfControlLibrary1:UserControl1 SelectedStyle="Style3" Content="This is the third control"></wpfControlLibrary1:UserControl1>
</StackPanel>
</Window>
The output below shows that the first control does not show a Style. If I run the application, switch it to Style2 using the Live editor and then back to Style1, the WingDings font does take over, but on a fresh run it does not. It definitely seems like a Dependency Property issue, but as far as I can tell I have the setup correct, especially since the other two controls seem to work.
Diagnosis
The reason why it does not work is because you assign the ReadOnlyStyle property value inside of SelectedStyle property changed handler. Since SelectedStyle is of type StyleSelector which is an enum, and you don't explicitly assign default value for this property, it will have default value of default(StyleSelector) assigned by the framework, which happens to be StyleSelector.Style1. And even if you explicitly assign that value to this property on your first control, the value doesn't really change, ergo the handler is not invoked, ergo ReadOnlyStyle remains null, ergo you get what you get (a Label with default style).
Solution
In order to remedy that, you should assign initial value of ReadOnlyStyle. But since the styles are kept in a resource dictionary, you cannot do that in the constructor. A good point to assign the initial value would be this:
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var style = (Style)userControl1.FindResource("SampleStyle-Font1");
SetValue(ReadOnlyStylePropertyKey, style);
}
Better solution
"The WPF way" of achieving your goal would be to use triggers. So first of all you could remove unnecessary code from your control:
public partial class UserControl1 : UserControl
{
public enum StyleSelector
{
Style1,
Style2,
Style3
}
public static DependencyProperty SelectedStyleProperty =
DependencyProperty.Register("SelectedStyle", typeof(StyleSelector), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public StyleSelector SelectedStyle
{
get => (StyleSelector)GetValue(SelectedStyleProperty);
set => SetValue(SelectedStyleProperty, value);
}
}
Then modify your temlpate:
<ControlTemplate>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=Command}">
<StackPanel>
<Label x:Name="PART_Label" Content="{Binding Path=Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}" />
</StackPanel>
</Button>
<ControlTemplate.Triggers>
<Trigger Property="local:UserControl1.SelectedStyle" Value="Style1">
<Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font1}" />
</Trigger>
<Trigger Property="local:UserControl1.SelectedStyle" Value="Style2">
<Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font2}" />
</Trigger>
<Trigger Property="local:UserControl1.SelectedStyle" Value="Style3">
<Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font3}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Two important things here are:
The Label needs to have x:Name so it can be referenced in Setter.TargetName
Trigger.Property value needs to be fully qualified, because ControlTemplate does not have TargetType set.
In WPF I'm trying to create a "flag" control that displays a checkmark or an X based on a bound dependency property (Flag)
<UserControl x:Name="Root" (Other user control stuff)>
<ContentControl Height="20" x:Name="flagHolder">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="False">
<Setter Property="Content" Value="{StaticResource XIcon}" />
<Setter Property="Foreground" Value="Crimson"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="True">
<Setter Property="Content" Value="{StaticResource CheckIcon}" />
<Setter Property="Foreground" Value="ForestGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UserControl>
On startup every icon is correct (I have several of these controls, each bound to different values). However, when I toggle a few (one that was "off" turns "on" and the one currently "on" turns "off") I see two things:
The control that was turned "on" has become a green check (as desired)
The control that was turned "off" is now just blank
Inspecting the visual tree seems to indicate that everything is working (though I could easily be missing something here), and the order of the triggers doesn't seem to matter. What am I doing wrong?
Here is an example icon, the path geometry is removed since its just noise:
<Viewbox x:Key="CheckIcon" x:Shared="False">
<Path Style="{StaticResource IconPathStyle}">
<Path.Data>
<PathGeometry Figures="Bunch of SVG" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
I'm unable to reproduce your issue, but here what I have and it's working:
App.xaml
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Viewbox x:Key="CheckIcon" x:Shared="False">
<Canvas Height="24" Width="32">
<Path Width="7.85446" Height="8.57578" Canvas.Left="-0.0522281" Canvas.Top="-0.100391" Stretch="Fill" StrokeThickness="1.04192" StrokeMiterLimit="2.75" Stroke="#FF000000" Data="F1 M 0.468732,4.66838L 3.03345,7.95443L 7.28127,0.420569"/>
</Canvas>
</Viewbox>
<Viewbox x:Key="XIcon" x:Shared="False">
<Canvas Height="24" Width="32">
<Path Data="M0,0 L1,1 M0,1 L1,0" Stretch="Fill" Stroke="Black" StrokeThickness="3" Width="12" Height="12" />
</Canvas>
</Viewbox>
</Application.Resources>
</Application>
YesNo.xaml
<UserControl x:Class="WpfApplication1.YesNo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Root">
<ContentControl Height="20" Name="flagHolder">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="False">
<Setter Property="Content" Value="{StaticResource XIcon}" />
<Setter Property="Foreground" Value="Crimson"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Root, Path=Flag}" Value="True">
<Setter Property="Content" Value="{StaticResource CheckIcon}" />
<Setter Property="Foreground" Value="ForestGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UserControl>
YesNo.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class YesNo : UserControl
{
public YesNo()
{
InitializeComponent();
}
public static readonly DependencyProperty FlagProperty = DependencyProperty.Register(
"Flag", typeof(bool), typeof(YesNo), new PropertyMetadata(default(bool)));
public bool Flag {
get {
return (bool) GetValue(FlagProperty);
}
set {
SetValue(FlagProperty, value);
}
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="" Width="400" Height="400">
<StackPanel Orientation="Vertical" Margin="50">
<wpfApplication1:YesNo Flag="{Binding Flag1}"/>
<wpfApplication1:YesNo Flag="{Binding Flag2}"/>
<wpfApplication1:YesNo Flag="{Binding Flag2}"/>
<wpfApplication1:YesNo Flag="{Binding Flag1}"/>
<Button Content="Toggle" Click="ButtonBase_OnClick"></Button>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : INotifyPropertyChanged
{
private bool _flag1;
private bool _flag2;
public MainWindow()
{
InitializeComponent();
DataContext = this;
Flag1 = true;
Flag2 = false;
}
public bool Flag1 {
get {
return _flag1;
}
set {
_flag1 = value;
OnPropertyChanged();
}
}
public bool Flag2 {
get {
return _flag2;
}
set {
_flag2 = value;
OnPropertyChanged();
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) {
Flag1 = !Flag1;
Flag2 = !Flag2;
}
}
How it looks like:
Video: http://www.screencast.com/t/J5IY7DR3Ry
I am creating an animation control and where I am trying to use data triggers. The issue is the dp property which I created is not getting changed/called when the trigger is fired. Here is the summary of behaviour I noticed.
1) The code behind of never gets called.
2) Property appears in XAML intellisense but the changes given in XAML never gets applied (design/runtime). But if I replace 'IsSpinning' in "public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(ProgressWaitSpinner), new UIPropertyMetadata(false));" to something else( say 'xyz') it starts working for property assignment, but throws runtime exception if styles are enabled.
3) When running the sample, The rectangle should be hidden instead of showing as Chocolate color, which is not happening.
4) Setter for changing color is working, which is from the user control, however the setter property on newly created property is not working.
I created a simplified sample here which shows the problem. Anyone got a clue what is going on please?
UserControl XAML:
<UserControl x:Class="CustomControls.ProgressWaitSpinner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls"
Height="191" Width="191">
<Grid x:Name="LayoutRoot">
<Label Height="32" Name="label1" VerticalAlignment="Top" />
</Grid>
</UserControl>
UserControl Code:
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class ProgressWaitSpinner : UserControl
{
public ProgressWaitSpinner(){InitializeComponent();}
public bool IsSpinning
{
get
{
return (bool)GetValue(IsSpinningProperty);
}
set
{
if (value == true)
{
this.Visibility = System.Windows.Visibility.Visible;
}
else
{
this.Visibility = System.Windows.Visibility.Hidden;
}
SetValue(IsSpinningProperty, value);
}
}
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(ProgressWaitSpinner), new UIPropertyMetadata(false));
}
}
MainWindow XAML:
<Window x:Class="WPFSpinnerWait.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:usrctrl="clr-namespace:CustomControls"
Title="MainWindow" Height="208" Width="228">
<Grid>
<usrctrl:ProgressWaitSpinner Height="40" x:Name="WaitSpinner" Margin="110,103,0,0" HorizontalAlignment="Left" Width="84" VerticalAlignment="Top">
<usrctrl:ProgressWaitSpinner.Style>
<Style>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="NotStarted"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="usrctrl:ProgressWaitSpinner.Background" Value="Red" />
<Setter Property="usrctrl:ProgressWaitSpinner.IsSpinning" Value="false"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="Running"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="usrctrl:ProgressWaitSpinner.Background" Value="Chocolate" />
<Setter Property="usrctrl:ProgressWaitSpinner.IsSpinning" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</usrctrl:ProgressWaitSpinner.Style>
</usrctrl:ProgressWaitSpinner>
<Button Content="NotStarted" Height="28" HorizontalAlignment="Left" Margin="38,22,0,0" Name="checkBox1" VerticalAlignment="Top" Width="136" Click="checkBox1_Checked" />
<Button Content="Running" Height="30" HorizontalAlignment="Left" Margin="38,56,0,0" Name="checkBox2" VerticalAlignment="Top" Width="136" Click="checkBox1_Checked" />
<Label Content="NotStarted" DataContext="usrctrl:ProgressWaitSpinner" Height="25" HorizontalAlignment="Left" Margin="38,92,0,0" Name="label1" VerticalAlignment="Top" Width="114" />
</Grid>
</Window>
MainWindow Code:
using System.Windows;
using System.Windows.Controls;
namespace WPFSpinnerWait
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void checkBox1_Checked(object sender, RoutedEventArgs e)
{
label1.Content = ((Button)sender).Content.ToString();
}
}
}
The code behind won't get called, DependancyProperties do not use the backing property when the property is change/used in Xaml, thay are only there for use in code behind as a helper, thay have no use in Xaml bindings
You can use the PropertyChanged event of the DependancyProperty instead
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSpinning. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSpinningProperty =
DependencyProperty.Register("IsSpinning", typeof(bool), typeof(ProgressWaitSpinner), new PropertyMetadata(false, OnIsSpinningChanged));
private static void OnIsSpinningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (((bool)e.NewValue) == true)
{
(d as ProgressWaitSpinner).Visibility = System.Windows.Visibility.Visible;
}
else
{
(d as ProgressWaitSpinner).Visibility = System.Windows.Visibility.Hidden;
}
}
Edit:
For your second question, Try adding the TargetType for your Style so you can access the properties directly
<Style TargetType="{x:Type usrctrl:ProgressWaitSpinner}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="NotStarted"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="IsSpinning" Value="false"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=label1, Path=Content}" Value="Running"></Condition>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Chocolate" />
<Setter Property="IsSpinning" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
I have a UserControl:
<UserControl x:Class="WpfApplication1.UserControl1"
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">
<Button Name="myButton" Content="hello world" Padding="10" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UserControl>
and code-behind:
public partial class UserControl1 : UserControl
{
public bool IsShown
{
get { return (bool)GetValue(IsShownProperty); }
set { SetValue(IsShownProperty, value); }
}
public static readonly DependencyProperty IsShownProperty =
DependencyProperty.Register("IsShown", typeof(bool), typeof(UserControl1), new UIPropertyMetadata(false));
public UserControl1()
{
InitializeComponent();
}
}
I want to add an Effect to myButton when IsShown is true. In the MainWindow,
<Window x:Class="WpfApplication1.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" xmlns:my="clr-namespace:WpfApplication1">
<Window.Resources>
<DropShadowEffect x:Key="outerGlow" Color="#00E300" Direction="0" ShadowDepth="0" BlurRadius="12" />
<Style x:Key="style" TargetType="my:UserControl1">
<Style.Triggers>
<Trigger Property="IsShown" Value="true">
<Setter Property="myButton.Effect" Value="{StaticResource outerGlow}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<my:UserControl1 x:Name="userControl11" Style="{StaticResource style}" />
</Window>
But VS cannot solve symbol myButton.
How to fix it?
Update:
I found the MSDN article. Can anyone tell me which category my property path falls in?
You can't do that.
If you need to affect a control which is inside the scope of a UserControl, you will need to define a property IN the UserControl and reference that in your inner control using RelativeSource or ElementName:
<UserControl x:Class="WpfApplication1.UserControl1"
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" x:Name="mycontrol">
<Button Name="myButton" Content="hello world" Padding="10" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsShown, ElementName="mycontrol"}" Value="True">
<Setter Property="Effect" Value"{StaticResource OrWhatever}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl>
When you need to set this effect in a Style that can be applied externally, you could create another property MyButtonEffect and set that in the Trigger.
public partial class UserControl1 : UserControl
{
public Effect MyButtonEffect
{
get { return (Effect)GetValue(MyButtonEffectProperty); }
set { SetValue(MyButtonEffectProperty, value); }
}
public static readonly DependencyProperty MyButtonEffectProperty =
DependencyProperty.Register("MyButtonEffect", typeof(Effect), typeof(UserControl1),
new FrameworkPropertyMetadata(null, MyButtonEffectPropertyChanged));
private static void MyButtonEffectPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((UserControl1)obj).myButton.Effect = (Effect)e.NewValue;
}
}
The Trigger:
<Trigger Property="IsShown" Value="true">
<Setter Property="MyButtonEffect" Value="{StaticResource outerGlow}"/>
</Trigger>