I've been banging my head against this problem for 3 days and haven't been able to find an answer.
I've got an application written in WPF (dot Net 4.5) and I'm working with Teststack.White trying to write some automated GUI test cases. The developers have given x:Names to some of the controls and they show up through Inspect/WPF Inspector/Visual UI Automation Verify as the AutomationId just fine.
There are other controls that are buried a bit deeper that I've been tasked with giving automation ids to (mostly as an exercise so I can get more familiar with the back-end). This is where I've been banging my head against things.
I've tried giving the controls the AutomationProperties.AutomationId (and .Name) attribute. I've given AutomationProperties a name space definition. As well I've made sure SWA.Peers is referenced.
I haven't tried using property setters in XAML as they don't make much sense currently and I'm hoping don't need to write stuff in C# to set them (if I do, I'll do it but just hoping).
One of my co-workers sat down and we pulled out the bare minimum setup that isn't exposing the Automation ID property (unfortunately he as well as the other devs are drawing a blank as to why this isn't happening). It looks like:
MainWindow.xaml:
<Window x:Class="TestAutomationUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:autoProp="clr-namespace:System.Windows.Automation;assembly=PresentationCore"
xmlns:common="clr-namespace:Asi.Ui.Common"
Title="Test UI (Pick me)" Height="455.075" Width="525">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/IconLabelButton.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel x:Name="PresentationRoot">
<common:IconLabelButton x:Name="TestButton" autoProp:AutomationProperties.AutomationId="TestButtonClick" Text="Stuff" Margin="245,0,214.4,0" RenderTransformOrigin="4.648,0.588" Height="32">
</common:IconLabelButton>
</StackPanel>
</Window>
IconLabelButton.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:autoProp="clr-namespace:System.Windows.Automation;assembly=PresentationCore"
xmlns:common="clr-namespace:Asi.Ui.Common">
<Style TargetType="common:IconLabelButton">
<Setter Property="Template" Value="{DynamicResource Asi.Ui.Common.IconLabelButton}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="FontWeight" Value="{DynamicResource Mobius.UI.Resources.Fonts.WeightLight}"/>
<Setter Property="Spacing" Value="10" />
</Style>
<ControlTemplate x:Key="Asi.Ui.Common.IconLabelButton" TargetType="common:IconLabelButton">
<Border Background="{TemplateBinding Background}" Height="30">
<Button Style="{DynamicResource Mobius.UI.Resources.Styles.IconButton}" Margin="0" Padding="0" HorizontalContentAlignment="Stretch" HorizontalAlignment="Left"
Command="{TemplateBinding Command}" CommandParameter="{TemplateBinding CommandParameter}" Foreground="{TemplateBinding Foreground}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="22" />
<ColumnDefinition Width="{TemplateBinding Spacing}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="22" Height="22">
<Path Margin="1" Height="20" Width="20" Fill="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ContentControl}}}" Data="{TemplateBinding Icon}" Stretch="Fill"/>
<Path Margin="1" Height="20" Width="20" Fill="{TemplateBinding AdornerIconFill}" Data="{TemplateBinding AdornerIcon}" Stretch="Fill"/>
</Grid>
<TextBlock Grid.Column="2" Text="{TemplateBinding Text}" VerticalAlignment="Center" Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ContentControl}}}" FontFamily="{TemplateBinding FontFamily}" FontSize="{TemplateBinding FontSize}" FontStretch="{TemplateBinding FontStretch}" FontWeight="{TemplateBinding FontWeight}"/>
</Grid>
</Button>
</Border>
</ControlTemplate>
</ResourceDictionary>
IconLabelButton.xaml.cs:
using System;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Asi.Ui.Common
{
public class IconLabelButton : Control
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(IconLabelButton), new PropertyMetadata(default(string)));
public static readonly DependencyProperty AdornerIconProperty =
DependencyProperty.Register("AdornerIcon", typeof(object), typeof(IconLabelButton), new PropertyMetadata(default(object)));
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(object), typeof(IconLabelButton), new PropertyMetadata(default(object)));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(IconLabelButton), new PropertyMetadata(default(ICommand)));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(IconLabelButton), new PropertyMetadata(default(ICommand)));
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register("Spacing", typeof(GridLength), typeof(IconLabelButton), new PropertyMetadata(default(GridLength)));
public static readonly DependencyProperty IconButtonSizeProperty =
DependencyProperty.Register("IconButtonSize", typeof(GridLength), typeof(IconLabelButton), new PropertyMetadata(default(GridLength)));
public static readonly DependencyProperty AdornerIconFillProperty =
DependencyProperty.Register("AdornerIconFill", typeof(Brush), typeof(IconLabelButton), new PropertyMetadata(default(Brush)));
static IconLabelButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconLabelButton), new FrameworkPropertyMetadata(typeof(IconLabelButton)));
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public object AdornerIcon
{
get { return GetValue(AdornerIconProperty); }
set { SetValue(AdornerIconProperty, value); }
}
public object Icon
{
get { return GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public GridLength Spacing
{
get { return (GridLength)GetValue(SpacingProperty); }
set { SetValue(SpacingProperty, value); }
}
public GridLength IconButtonSize
{
get { return (GridLength)GetValue(IconButtonSizeProperty); }
set { SetValue(IconButtonSizeProperty, value); }
}
public Brush AdornerIconFill
{
get { return (Brush)GetValue(AdornerIconFillProperty); }
set { SetValue(AdornerIconFillProperty, value); }
}
}
}
I apologize if this is an easy question (or if I'm not asking the right one). I'm an entry-level programmer who only has a cursory familiarity with WPF/XAML.
Thanks in advance for the help! (Hopefully it's any easy fix!)
Update: After doing some further digging on UI Automation Providers, it looks like all custom controls have to be written to support AutomationProperties. Of course talking with the devs that isn't case.
I'll post more information when I get the solution figured out.
There is no AutomationPeer for your control. So your AutomationId setup does not set anything.
Override OnCreateAutomationPeer in your control codebehind like this:
protected override AutomationPeer OnCreateAutomationPeer()
{
return new IconLabelButtonAutomationPeer(this);
}
where your new IconLabelButtonAutomationPeer looks like this:
public class IconLabelButtonAutomationPeer : FrameworkElementAutomationPeer
{
public IconLabelButtonAutomationPeer(IconLabelButton owner)
: base(owner)
{
}
}
Related
I'm trying to bind a property from a user control to a window but get error
Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'.
When I was searching the error usually the people didn't create a Dependency Property or that the problem may be because the two Way binding but didn't find a solution
I know that if I give the usercontrol a name and then get the value but I want to use binding
UserControl.xaml
<UserControl x:Name="ctb" x:Class="ChatClient.CustomControls.CustomTextBox"
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:ChatClient.CustomControls"
mc:Ignorable="d"
d:DesignHeight="20" d:DesignWidth="350">
<Grid>
<Border CornerRadius="8"
Background="#3e4147">
<Grid>
<TextBox VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
HorizontalAlignment="Stretch"
Background="Transparent"
x:Name="TextBox"
TextWrapping="Wrap"
BorderThickness="0"
Foreground="Gray"
CaretBrush="Gray"
Margin="8,0,0,0"
Text="{Binding ElementName=ctb, Path=InputText}">
</TextBox>
<TextBlock IsHitTestVisible="False"
Text="{Binding ElementName=ctb, Path=PlaceHolder}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10,0,0,0"
Foreground="DarkGray">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=TextBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</Grid>
</UserControl>
UserControl.xaml.cs
public CustomTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty InputTextProp = DependencyProperty.Register(
nameof(InputText),
typeof(string),
typeof(CustomTextBox),
new PropertyMetadata(OnValueChangedText));
public static readonly DependencyProperty PlaceHolderProp = DependencyProperty.Register("PlaceHolder", typeof(string),
typeof(CustomTextBox),
new PropertyMetadata(null));
public string InputText
{
get { return (string)GetValue(InputTextProp); }
set { SetValue(InputTextProp, value); }
}
public string PlaceHolder
{
get { return (string)GetValue(PlaceHolderProp); }
set { SetValue(PlaceHolderProp, value); }
}
private static void OnValueChangedText(DependencyObject sender,DependencyPropertyChangedEventArgs e)
{
CustomTextBox customTextBox = sender as CustomTextBox;
customTextBox.InputText = (string)e.NewValue;
}
MainWindow.xaml
<customcontrols:CustomTextBox InputText="{Binding UserName}"
PlaceHolder="UserName"
Margin="0,0,0,20" />
Dependency property name must end with "Property", not "Prop":
public static readonly DependencyProperty InputTextProperty = DependencyProperty.Register
(
nameof(InputText),
typeof(string),
typeof(CustomTextBox),
new PropertyMetadata(string.Empty)
);
fix it for all usages and all properties.
OnValueChangedText callback implemented incorrectly and should be removed from metadata
I have the following WPF Control which is used to Show A Text With An Image Beside it
XAML Code
<UserControl x:Class="WFWorkSpaceWPF.UserControls.StackedImageTextCtl"
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"
Name="StackedImageText"
>
<Grid>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=StackedImageText, Path=ImageSource}" />
<TextBlock Text="{Binding ElementName=StackedImageText, Path=Text}" />
</StackPanel>
</Grid>
CS
public partial class StackedImageTextCtl : UserControl
{
public StackedImageTextCtl()
{
InitializeComponent();
}
#region "Properties"
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StackedImageTextCtl), new UIPropertyMetadata(""));
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(StackedImageTextCtl), new UIPropertyMetadata(null));
#endregion
}
In my project i want to reuse this control in other three user controls where it will be added as a part of these controls, As you see that the StackedImageTextCtl exposes Two Properties (Text and Image Source) that the parent User Controls needs to provide to it and these three controls will take the value from the container window throw XAML, I know that One of the ways to do so is to replicate defining the properties in each of these three user control and Using AddOwner functionality, but i am looking for a more better approach that won't require any repeat in code, can anyone please direct me to such way?.
This is your UserControl:
<UserControl ...>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" Width="16"/>
<TextBlock Text="{Binding Text}" />
</StackPanel>
</UserControl>
and its code:
in addition to Text and ImageSource, there is State which represents the Add/Edit/delete state of this control, and there is StackCtlState which is an attached property and when attached to a FrameworkElement it represents the Add/Edit/delete state of that control.
public StackedImageTextCtl()
{
InitializeComponent();
DataContext = this;
}
//Text Dependency Property
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StackedImageTextCtl), new UIPropertyMetadata(null));
//ImageSource Dependency Property
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(StackedImageTextCtl), new UIPropertyMetadata(null));
//State Dependency Property
public AddEditDelete State
{
get { return (AddEditDelete)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(AddEditDelete), typeof(StackedImageTextCtl), new UIPropertyMetadata(AddEditDelete.Add));
public static AddEditDelete GetStackCtlState(DependencyObject obj)
{
return (AddEditDelete)obj.GetValue(StackCtlStateProperty);
}
public static void SetStackCtlState(DependencyObject obj, AddEditDelete value)
{
obj.SetValue(StackCtlStateProperty, value);
}
public static readonly DependencyProperty StackCtlStateProperty =
DependencyProperty.RegisterAttached("StackCtlState", typeof(AddEditDelete), typeof(StackedImageTextCtl), new UIPropertyMetadata(AddEditDelete.Add));
I also define an enum:
public enum AddEditDelete { Add, Edit, Delete }
In Window xaml:
Each Button or ToggleButton have their attached property StackCtlState set to the desired value and also have their style set to one of styles for Button or ToggleButton.
Then these styles add a StackedImageTextCtl to the contents of styled button/togglebutton in the proper way that resources can be reused. (if you set just Content and don't set Template instead it will only show the contents of the last Button or last ToggleButton) The added StackedImageTextCtl has a State equal to the attached value of its TemplatedParent which is Button or ToggleButton.
At last the style uses Trigger to set the values for Text and Image, based on State of the StackedImageTextCtl.
<Window...
xmlns:myNamespace="clr-namespace:WpfApplication1">
<Window.Resources>
<Style TargetType="{x:Type myNamespace:StackedImageTextCtl}">
<Style.Triggers>
<Trigger Property="State" Value="Add">
<Setter Property="Text" Value="ADD"/>
<Setter Property="ImageSource" Value="/blue_1.jpg"/>
</Trigger>
<Trigger Property="State" Value="Edit">
<Setter Property="Text" Value="EDIT"/>
<Setter Property="ImageSource" Value="/blue_2.jpg"/>
</Trigger>
<Trigger Property="State" Value="Delete">
<Setter Property="Text" Value="DELETE"/>
<Setter Property="ImageSource" Value="/blue_3.jpg"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="stackButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button>
<myNamespace:StackedImageTextCtl State="{TemplateBinding myNamespace:StackedImageTextCtl.StackCtlState}"/>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="stackToggleButtonStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ToggleButton>
<myNamespace:StackedImageTextCtl State="{TemplateBinding myNamespace:StackedImageTextCtl.StackCtlState}"/>
</ToggleButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<Button Style="{StaticResource stackButtonStyle}" myNamespace:StackedImageTextCtl.StackCtlState="Add"/>
<Button Style="{StaticResource stackButtonStyle}" myNamespace:StackedImageTextCtl.StackCtlState="Edit"/>
<ToggleButton Style="{StaticResource stackToggleButtonStyle}" myNamespace:StackedImageTextCtl.StackCtlState="Delete"/>
</StackPanel>
I have created an UserControl to implement a simple ImageButton as following:
<UserControl x:Class="MyApp.Common.Controls.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="UC">
<Grid>
<Button Command="{Binding ElementName=UC, Path=Command}" CommandParameter="{Binding ElementName=UC, Path=CommandParameter}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=UC, Path=Image}"
Width="{Binding ElementName=UC, Path=ImageWidth}"
Height="{Binding ElementName=UC, Path=ImageHeight}"/>
<TextBlock Text="{Binding ElementName=UC, Path=Text}" />
</StackPanel>
</Button>
</Grid>
Here is code-behind of my control:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace MyApp.Common.Controls
{
public partial class ImageButton : UserControl
{
public ImageButton()
{
InitializeComponent();
}
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
public double ImageWidth
{
get { return (double)GetValue(ImageWidthProperty); }
set { SetValue(ImageWidthProperty, value); }
}
public static readonly DependencyProperty ImageWidthProperty =
DependencyProperty.Register("ImageWidth", typeof(double), typeof(ImageButton), new UIPropertyMetadata(16d));
public double ImageHeight
{
get { return (double)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public static readonly DependencyProperty ImageHeightProperty =
DependencyProperty.Register("ImageHeight", typeof(double), typeof(ImageButton), new UIPropertyMetadata(16d));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ImageButton), new UIPropertyMetadata(""));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ImageButton));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ImageButton));
}
}
The usage is simple:
<cc:ImageButton Command="{Binding SearchCommand}"
Image="/MyApp;component/Images/magnifier.png"
ImageWidth="16" ImageHeight="16"
/>
When the button (bound to DelegateCommand in my ViewModel) get disabled the image is disappear. Otherwise all works as expected.
What could be a problem?
How to make the image show in gray-scale when disabled?
UPDATE: here is the image of this strange behavior:
I copied the code that you had provided to see if I could reproduce the problem you were having. Unfortunately, when the command was disabled (or it's CanExecute was returning false) the image I used did not disappear. Could you please provide more code from your ViewModel that you think may be relevant?
To answer the second part of your question:
How to make the image show in gray-scale when disabled?
As far as I know there is no easy way to desaturate an image in WPF. Instead, I would go with the approach that #Vova had suggested which is to lower the Opacity property of the Image. You can modify your UserControl XAML you provided like so:
<UserControl x:Class="MyApp.Common.Controls.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="UC">
<Grid>
<Button Command="{Binding ElementName=UC, Path=Command}" CommandParameter="{Binding ElementName=UC, Path=CommandParameter}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ElementName=UC, Path=Image}"
Width="{Binding ElementName=UC, Path=ImageWidth}"
Height="{Binding ElementName=UC, Path=ImageHeight}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}} }" Value="false" >
<Setter Property="Opacity" Value="0.3" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding ElementName=UC, Path=Text}" />
</StackPanel>
</Button>
</Grid>
</UserControl>
In the code above I added a DataTrigger to the Image's Style property to modify the opacity of the image if the IsEnabled property of the button is equal to false.
Hope this helps!
As mentioned before, there is no built-in desaturation support in WPF, but you can easily use a shader effect to implement that. The link takes you to an implementation that lets you use the effect in a XAML-only way.
Please note that for shaders written in HLSL need to be compiled and for compilation you will need the Direct X SDK installed on your machine. Quite a beast for such a small task, admitted.
I would make it custom control,make a trigger for it at Property =isEnabled value=false. Over the image,I would add a grid with some opacity,and control that opacity or visibility with that trigger. Good luck. I don't think there is an easier way.
I've a custom control look like this:
generic.xaml
<Style TargetType="controls:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:MyControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
Text="{Binding ElementName=slider, Path=Value}" />
<Slider Grid.Row="1" Name="slider" Width="120"
Minimum="1" Maximum="12"
Value="{Binding Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent},
Path=Value}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MyControl.cs
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(double),
typeof(MyControl),
new PropertyMetadata(0d, OnValueChanged));
public double Value
{
get { return (double)base.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
private static void OnValueChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
MyControl myControl = (MyControl)source;
myControl.OnValueChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual void OnValueChanged(double oldValue, double newValue)
{
double coercedValue = CoerceValue(newValue);
if (coercedValue != newValue)
{
this.Value = coercedValue;
}
}
private double CoerceValue(double value)
{
double limit = 7;
if (value > limit)
{
return limit;
}
return value;
}
The TextBox is just a dummy to show the value.
Now when I add this control to an Application, I am able to set the Slider value greater than 7, although the value of my DependencyProperty is set to 7.
What I am doing wrong? Does the TwoWayBinding does not work in this situation?
Thanks in advance
Steps for my repro:-
Create a fresh new Silverlight Application in VS2010 call SilverlightApplication1.
Add new "Silverlight Templated Control" to the silverlight project, naming it "MyControl".
Copied the inner contents or you ControlTemplate into the ControlTemplate of the themes/Generic.xaml file. This Entire generic file looks like:-
<Style TargetType="local:MyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
Text="{Binding ElementName=slider, Path=Value}" />
<Slider Grid.Row="1" Name="slider" Width="120"
Minimum="1" Maximum="12"
Value="{Binding Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent},
Path=Value}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Copied your C# placed in in MyControl.cs. The whole file looks like:-
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public class MyControl : Control
{
public MyControl()
{
this.DefaultStyleKey = typeof(MyControl);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(double),
typeof(MyControl),
new PropertyMetadata(0d, OnValueChanged));
public double Value
{
get { return (double)base.GetValue(ValueProperty); }
set { base.SetValue(ValueProperty, value); }
}
private static void OnValueChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
MyControl myControl = (MyControl)source;
myControl.OnValueChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual void OnValueChanged(double oldValue, double newValue)
{
double coercedValue = CoerceValue(newValue);
if (coercedValue != newValue)
{
this.Value = coercedValue;
}
}
private double CoerceValue(double value)
{
double limit = 7;
if (value > limit)
{
return limit;
}
return value;
}
}
}
Added an instance of MyControl to MainPage.xaml, which now looks like:-
<UserControl x:Class="SilverlightApplication1.MainPage"
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:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<local:MyControl />
</Grid>
</UserControl>
Run the solution, works fine.
I created the following xaml:
<Button x:Name="PriceButton">
<Button.Template>
<ControlTemplate>
<Border x:Name="ButtonBorder"
CornerRadius="2"
Background="{StaticResource DarkReflectionBrush}"
BorderBrush="Black"
BorderThickness="1" HorizontalAlignment="Stretch">
<ContentPresenter
VerticalAlignment="Center"
HorizontalAlignment="Left">
<ContentPresenter.Content>
<Grid x:Name="ContentGrid" HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="15*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="Price" Grid.Column="0" FontFamily="Calibri" FontSize="8"
VerticalAlignment="Bottom" Text="{Binding Path=PriceText}"
Foreground="{Binding Path=PriceColor}" ></TextBlock>
<TextBlock x:Name="Main" Grid.Column="1" FontFamily="Calibri" FontSize="14"
Text="{Binding Path=MainText}"
Foreground="{Binding Path=MainTextColor}" />
<TextBlock x:Name="Vol" Grid.Column="2" FontFamily="Calibri" FontSize="8"
VerticalAlignment="Bottom" Text="{Binding Path=VolatilityText}"
Foreground="{Binding Path=VolatilityColor}" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsPressed" Value="true">
<Setter TargetName="ButtonBorder" Property="Background" Value="{StaticResource PressedBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
Here's the Code Behind:
public static readonly DependencyProperty PriceTextProperty =
DependencyProperty.Register(
"PriceText",
typeof (string),
typeof (LivePriceVolButton),
new FrameworkPropertyMetadata(string.Empty, OnPriceTextChanged));
public string PriceText {
get {
return (string)GetValue(PriceTextProperty);
}
set {
SetValue(PriceTextProperty, value);
}
}
public static readonly DependencyProperty PriceColorProperty =
DependencyProperty.Register(
"PriceColor",
typeof (Color),
typeof (LivePriceVolButton),
new FrameworkPropertyMetadata(Colors.White));
public Color PriceColor {
get {
return (Color) GetValue(PriceColorProperty);
}
set {
SetValue(PriceColorProperty, value);
}
}
public static readonly DependencyProperty MainTextProperty =
DependencyProperty.Register(
"MainText",
typeof (string),
typeof (LivePriceVolButton),
new FrameworkPropertyMetadata(string.Empty));
public string MainText {
get {
return (string) GetValue(MainTextProperty);
}
set {
SetValue(MainTextProperty, value);
}
}
public static readonly DependencyProperty MainTextColorProperty =
DependencyProperty.Register(
"MainTextColor",
typeof(Color),
typeof(LivePriceVolButton),
new FrameworkPropertyMetadata(Colors.White));
public Color MainTextColor {
get {
return (Color) GetValue(MainTextColorProperty);
}
set {
SetValue(MainTextColorProperty, value);
}
}
public static readonly DependencyProperty VolatilityTextProperty =
DependencyProperty.Register(
"VolatilityText",
typeof(string),
typeof(LivePriceVolButton),
new FrameworkPropertyMetadata(string.Empty));
public string VolatilityText {
get {
return (string) GetValue(VolatilityTextProperty);
}
set {
SetValue(VolatilityTextProperty, value);
}
}
public static readonly DependencyProperty VolatilityColorProperty =
DependencyProperty.Register(
"VolatilityColor",
typeof(Color),
typeof(LivePriceVolButton),
new FrameworkPropertyMetadata(Colors.White));
public Color VolatilityColor {
get {
return (Color) GetValue(VolatilityColorProperty);
}
set {
SetValue(VolatilityColorProperty, value);
}
}
When I insert my user control onto a form like this
<my:LivePriceVolButton Margin="43.03,0,0,32" x:Name="livePriceVolButton1"
xmlns:my="clr-namespace:MultiTextBlockButton" HorizontalAlignment="Left"
Width="91.703" Height="30" VerticalAlignment="Bottom"
MainText="MID" MainTextColor="LightBlue" PriceColor="Red" PriceText="1234.56"
VolatilityText="12.2%" VolatilityColor="Aqua" />
I don't see anything in the button at all. Any ideas?
Thanks
You have to set the DataContext for the Button to be equal to the parent UserControl for your Bindings to work. Try something like this:
<UserControl x:Name="uc" ...>
<Button x:Name="PriceButton" DataContext="{Binding ElementName=uc}">
<!--Other code here...-->
</Button>
</UserControl>
I also see that you're using "Color" as the Type for some of your DependencyProperties. I suggest you change them to "Brush" instead. Otherwise the related bindings (e.g. Foreground="{Binding VolatilityColor}") won't work.