Fired datatrigger is not changing custom control's newly added property - c#

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>

Related

WPF Set property of element in Style (Visual.Brush)

How can I set the Content property of the Label (L_Watermark)?
I tried different ways but nothing work.
Normally to set a property of an element about his name but here it doesn´t work.
I want do do it in the CodeBehind or with Databinding to a dependency property
The XAML of my UserControl:
<StackPanel Orientation="Horizontal" Width="155">
<TextBox x:Name="TB_Date" LostFocus="TB_Date_LostFocus" KeyDown="TB_Date_KeyDown" BorderThickness="0" VerticalContentAlignment="Center" Width="125" Height="24">
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Name="VisuBrush" x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label x:Name="L_Watermark" Content="{Binding Watermark, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, FallbackValue=Date}" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<DatePicker Name="DP_Date" Focusable="False" BorderBrush="{x:Null}" SelectedDateChanged="DP_Date_SelectedDateChanged" Width="30" FirstDayOfWeek="Monday" Height="24"/>
</StackPanel>
Here is the eventhandler in which i want to set the Content property of the L_Watermark label
private void Initialize(object sender, EventArgs e)
{
if (HasStartTime)
{
TB_Date.Text = DateTime.Now.ToLongDateString();
}
Debug.WriteLine(Watermark);
}
Like the Text in TB_Date i tried to to it with L_Watermark.Content = Watermark
I also tried to do it with Databinding like:
<Label x:Name="L_Watermark" Content="{Binding Watermark, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, FallbackValue=Date}" Foreground="LightGray" />
I also have two dependency properties in my UserControl:
public bool HasStartTime
{
get { return (bool)GetValue(HasStartTimeProperty); }
set
{
SetValue(HasStartTimeProperty, value);
OnPropertyChanged();
}
}
// Using a DependencyProperty as the backing store for HasStartTime. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasStartTimeProperty =
DependencyProperty.Register("HasStartTime", typeof(bool), typeof(AdvancedDatePicker), new PropertyMetadata(false));
public string Watermark
{
get { return (string)GetValue(WatermarkProperty); }
set
{
SetValue(WatermarkProperty, value);
OnPropertyChanged();
}
}
// Using a DependencyProperty as the backing store for Watermark. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WatermarkProperty =
DependencyProperty.Register("Watermark", typeof(string), typeof(AdvancedDatePicker), new PropertyMetadata("Datum"));

Why do my triggers end up with a blank control?

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

Changing Image in User Control with DataTrigger

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!

Setting the content property dynamically using a custom property XAML WPF

I'm creating an onscreen keyboard for a touch screen app, where shift toggles upper and lower case buttons on the whole keyboard.
The code in the c# is working but I don't know how to change the content value and command parameter of the buttons based on on my custom property which changes on a bool value, in xaml.
<local:KeyboardButton Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Command="{Binding AddText}" Content ="{Binding local:KeyboardButton.SelectedKey}" LowerCaseKey="`" UpperCasekey="¬"/>
This is what I have currently for each button in the XAML (ignore the Content, as I've been grasping at straws here), the idea is that the shift key will toggle the Content and CommandParameter between the LowerCaseKey and UpperCaseKey properties.
maybe you could achieve your goal with styles and triggers:
<Button Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Command="{Binding AddText}" x:Name="AButton">
<Button.Resources>
<Style TargetType="Button">
<Setter Property="Content" Value="{Binding Path=LowerCaseKey, ElementName=AButton}" />
<Setter Property="CommandParameter" Value="{Binding Path=LowerCaseKey, ElementName=AButton}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsUpperCase}" Value="true">
<Setter Property="Content" Value="{Binding Path=UpperCasekey, ElementName=AButton}" />
<Setter Property="CommandParameter" Value="{Binding Path=UpperCasekey, ElementName=AButton}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Resources>
</Button>
Custom Control:
using System.Windows;
using System.Windows.Controls;
namespace Test
{
public class KeyboardButton : Button
{
public static readonly DependencyProperty SelectedKeyProperty = DependencyProperty.Register("SelectedKey", typeof(string),
typeof(KeyboardButton), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsArrange));
public static readonly DependencyProperty IsUpperCaseProperty = DependencyProperty.Register("IsUpperCase", typeof(bool),
typeof(KeyboardButton), new FrameworkPropertyMetadata(false));
static KeyboardButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(KeyboardButton), new FrameworkPropertyMetadata(typeof(KeyboardButton)));
}
public string SelectedKey
{
get { return (string)GetValue(SelectedKeyProperty); }
set { SetValue(SelectedKeyProperty, value); }
}
public string LowerCaseKey
{
get;
set;
}
public string UpperCaseKey
{
get;
set;
}
public bool IsUpperCase
{
get { return (bool)GetValue(IsUpperCaseProperty); }
set { SetValue(IsUpperCaseProperty, value); }
}
}
}
Themes\Generic.xaml (file Generic.xaml in the Themes folder)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test">
<Style TargetType="{x:Type local:KeyboardButton}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content" Value="{Binding LowerCaseKey, Mode=OneTime, RelativeSource={RelativeSource Self}}"/>
<Style.Triggers>
<Trigger Property="IsUpperCase" Value="true">
<Setter Property="Content" Value="{Binding UpperCaseKey, Mode=OneTime, RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Don't forget this in AssemblyInfo.cs:
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

Troubleshooting Binding Error 4

I'm getting the following Binding errors on my code and I don't know how to troubleshoot them. The bindings were generated by VS. I've tried adding presentation.tracesources (which is in the code below) but I get the same output as before.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='ClimateSolutions.SuperTB', AncestorLevel='1''. BindingExpression:Path=myName; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='ClimateSolutions.SuperTB', AncestorLevel='1''. BindingExpression:Path=isRequired; DataItem=null; target element is 'SuperTB' (Name='email'); target property is 'NoTarget' (type 'Object')
Here's my XAML:
<TextBox x:Class="ClimateSolutions.SuperTB"
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" Height="53" Width="296" FontSize="32"
xmlns:local="clr-namespace:ClimateSolutions"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
HorizontalAlignment="Left" Name="Blarg">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Foreground="Gray" FontSize="24" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:SuperTB, AncestorLevel=1}, Path=myName, diagnostics:PresentationTraceSources.TraceLevel=High}">
</TextBlock>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
<DataTrigger Binding="{Binding Path=isRequired, RelativeSource={RelativeSource FindAncestor, AncestorType=local:SuperTB, AncestorLevel=1}}" Value="False">
<Setter Property="Text" Value="100" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
and here's the CS for SuperTB:
namespace ClimateSolutions
{
/// <summary>
/// Interaction logic for SuperTB.xaml
/// </summary>
public partial class SuperTB : TextBox, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String Property)
{
var anEvent = this.PropertyChanged;
if (anEvent != null)
{
anEvent(this, new PropertyChangedEventArgs(Property));
}
}
private String MyName = "Unicorns!";
private static DependencyProperty myNameProperty = DependencyProperty.Register("myName", typeof(String), typeof(SuperTB));
public String myName
{
get { return MyName; }
set { MyName = value; NotifyPropertyChanged("myName"); }
}
DependencyProperty isRequiredProperty = DependencyProperty.Register("isRequired", typeof(Boolean), typeof(SuperTB));
public Boolean isRequired
{
get { return (Boolean)GetValue(isRequiredProperty); }
set { SetValue(isRequiredProperty, value); }
}
public SuperTB()
{
InitializeComponent();
myName = "Unicorns!";
}
}
}
EDIT : I have updated the code according to your comment. To summarize, since this is a custom control, you are less dependant on the MVVM pattern to build your component logic (and thus use code behind in you component) as soon as your componennt itself meets this needs (to be sort, make its properties to be as much bindable as you can). For example, in the updated code, you can now bind the default property, but you can also imagine exposing properties to set the foreground colors used to diaplay control name when there is no value, and so forth.
I tried several things with you original code (included solution provided by J cooper) and nothing seemed to work. It seems that there is a lot of issues with your code.
I managed to approach a solution by making your textbox a custom control.
Here is the Generic.xaml (the visual definition of your control) :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Question_6514447">
<Style TargetType="{x:Type local:SuperTB2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SuperTB2}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox x:Name="PART_Input">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsRequired}" Value="False">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DefaultTextValue}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And here is the code behind of the control :
[TemplatePart(Name = "PART_Input")]
public class SuperTB2 : Control
{
private TextBox PART_Input;
static SuperTB2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SuperTB2), new FrameworkPropertyMetadata(typeof(SuperTB2)));
}
public SuperTB2()
{
Loaded += SuperTb2Loaded;
}
public override void OnApplyTemplate()
{
PART_Input = GetTemplateChild("PART_Input") as TextBox;
if (PART_Input != null)
{
PART_Input.GotFocus += PartInputGotFocus;
PART_Input.LostFocus += PartInputLostFocus;
}
}
void PartInputLostFocus(object sender, RoutedEventArgs e)
{
if (PART_Input.Text == string.Empty)
{
PART_Input.Text = Name;
PART_Input.Foreground = new SolidColorBrush(Colors.Gray);
}
}
void PartInputGotFocus(object sender, RoutedEventArgs e)
{
if (PART_Input.Text.Equals(Name))
{
PART_Input.Text = string.Empty;
PART_Input.Foreground = new SolidColorBrush(Colors.Black);
}
}
void SuperTb2Loaded(object sender, RoutedEventArgs e)
{
if (PART_Input.Text == string.Empty)
{
PART_Input.Text = Name;
PART_Input.Foreground = new SolidColorBrush(Colors.Gray);
}
}
private static DependencyProperty myNameProperty =
DependencyProperty.Register("MyName", typeof(string), typeof(SuperTB2), new PropertyMetadata("Unicorns !", NameChanged));
private static void NameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public string MyName
{
get { return (string)GetValue(myNameProperty); }
set { SetValue(myNameProperty, value); }
}
DependencyProperty isRequiredProperty =
DependencyProperty.Register("IsRequired", typeof(bool), typeof(SuperTB2), new PropertyMetadata(false, IsReqChanged));
private static void IsReqChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public bool IsRequired
{
get { return (bool)GetValue(isRequiredProperty); }
set { SetValue(isRequiredProperty, value); }
}
public string DefaultTextValue
{
get { return (string)GetValue(DefaultTextValueProperty); }
set { SetValue(DefaultTextValueProperty, value); }
}
// Using a DependencyProperty as the backing store for DefaultTextValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DefaultTextValueProperty =
DependencyProperty.Register("DefaultTextValue", typeof(string), typeof(SuperTB2), new UIPropertyMetadata("100"));
}
And an example of use of the component :
<Grid>
<StackPanel>
<Question_6514447:SuperTB2 x:Name="FirstName" IsRequired="true" DefaultTextValue="200"/>
</StackPanel>
</Grid>
With this updated code, I think you can acheive almost all the behaviors your needed !
Hope this will help !
Do not use relative source in your binding expressions. Relative source is used to access elements higher in the element tree. It seems as though you were using it in terms of object inheritance.
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Foreground="Gray" FontSize="24" Text="{Binding Path=myName, diagnostics:PresentationTraceSources.TraceLevel=High}">
</TextBlock>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
<DataTrigger Binding="{Binding Path=isRequired}" Value="False">
<Setter Property="Text" Value="100" />
</DataTrigger>

Categories

Resources