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.
Related
I have extended ListView class and created two DataTemplate for it in the separate Resource file. My question is how I can add event handlers for the Checkbox (and other items) in the DataTemplate?
MyListView.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
public class MyListView : ListView
{
public enum ListMode
{
List, ListCheck
}
public static readonly DependencyProperty ModeProperty = DependencyProperty.Register
(
"Mode",
typeof(ListMode),
typeof(MyListView),
new PropertyMetadata(ListMode.List)
);
public ListMode Mode
{
get { return (ListMode)GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
}
}
MyListViewItem.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
public class MyListViewItem:Control
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register
(
"Text",
typeof(string),
typeof(MyListViewItem),
new UIPropertyMetadata(string.Empty)
);
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register
(
"IsChecked",
typeof(bool),
typeof(MyListViewItem),
new UIPropertyMetadata(false)
);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
}
ResourceDictionary.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<DataTemplate x:Key="ItemTemplate_List">
<TextBlock Text="{Binding Text}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate_ListCheck">
<Grid>
<CheckBox IsChecked="{Binding IsChecked}"/>
<TextBlock Text="{Binding Text}" Margin="20,0,0,0" HorizontalAlignment="Left"/>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type local:MyListView}">
<Style.Triggers>
<Trigger Property="Mode" Value="List">
<Setter Property="ItemTemplate" Value="{StaticResource ItemTemplate_List}"/>
</Trigger>
<Trigger Property="Mode" Value="ListCheck">
<Setter Property="ItemTemplate" Value="{StaticResource ItemTemplate_ListCheck}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Your question refers to Event handlers and the use of them in Resource files. Access to the code-behind cannot be expressed directly in XAML stored in resource files.
While you are able to do the following in the XAML of a UserControl,
<CheckBox Click="CheckBox_OnClick"/>
this is simply not possible when the style is detached in a separate resource directory.
This is where the ICommand interface becomes useful to trigger, usually, on the ViewModel but can also be used to trigger code-behind. The syntax then becomes (if you are looking to trigger code-behind):-
<CheckBox Command="{Binding CheckBoxClickedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType='MyListView'}}"/>
This would allow you to trigger code in your MyListView class if you:-
Add a property called CheckBoxClickedCommand to your MyListView class
Add the supporting interface for ICommand on that property of that class
It is common to use the RelayCommand implementation so you can supply Lambda Expressions to simplify the implementation. A quick search on RelayCommand will give you some guidance.
This all sounds good until you find out that only certain controls (e.g. Button, Checkbox, RadioButton) implement the Command pattern as standard.
Luckily since Blend 3 we now have more scope to use the ICommand pattern using Behaviours, again something you might want to research.
<CheckBox Command="{Binding CheckBoxClickedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding CheckBoxMouseEnterCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Checkbox>
Here other events such as MouseEnter can be used to fire off an ICommand call. This also means other controls that did not support the Command= property can also call the code-behind.
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)
{
}
}
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>
There are similar questions to this one on here however I have tried the mentioned solutions to no avail!
Let me just run you through the setup - I have a model which implements IDataErrorInfo, a viewmodel which exposes the model to the view, within the view I have a usercontrol which is simply a labelled textbox, the model properties are binded to the usercontrol's inner textbox via a dependency property... and everything is binding correctly, all validation is fired and the correct errors returned! However, the usercontrol appears to be intercepting the error and thus the errortemplate of the usercontrol is displayed and not the textbox.
So, I know I can stop the usercontrol's error template from being displayed by setting the property to x:Null, however how do I trigger the textbox's error template to be displayed?! I have tried implementing IDataErrorInfo within the usercontrol (as advised by some) and explicitly defining the validation error template within the user control but I just can't get the darn thing to display. At this point I am thinking that the usercontrol is simply intercepting the error, holding onto it and not passing it onto the textbox, hence the errortemplate not being shown as it isn't aware of the error.
I have been pulling my hair out for the past day and really don't want to resort to not using the usercontrol as I know this can be achieved but I really don't know how to fix it! So if there are any wizards out there that can help I would be very grateful!
UserControl XAML:
<UserControl x:Class="PIRS_Client.Control.LabelTextBox"
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="40.541" Width="321.027">
<Grid Height="41" VerticalAlignment="Top" HorizontalAlignment="Left" Width="321">
<StackPanel Orientation="Horizontal" Margin="0,8,50,9">
<Label Content="Label" Height="28" Name="BaseLabel" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="116" FontSize="11" />
<TextBox Height="22" Width="100" Margin="0,0,0,0" x:Name="BaseTextBox" VerticalContentAlignment="Center" VerticalAlignment="Top" FontSize="11"/>
</StackPanel>
</Grid>
UserControl Code:
public partial class LabelTextBox : UserControl
{
public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register("TextBoxText", typeof(string), typeof(LabelTextBox), new FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });
public LabelTextBox()
{
InitializeComponent();
Binding textBoxText = new Binding("TextBoxText") { Source = this, Mode = BindingMode.TwoWay };
BaseTextBox.SetBinding(TextBox.TextProperty, textBoxText);
}
[Browsable(true)]
public string LabelText
{
get { return BaseLabel.Content.ToString(); }
set
{
BaseLabel.Content = value;
}
}
[Browsable(true)]
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
[Browsable(true)]
public double TextBoxWidth
{
get { return BaseTextBox.Width; }
set
{
BaseTextBox.Width = value;
}
}
}
View - UserControl delcaration:
<control:LabelTextBox HorizontalAlignment="Left" LabelText="Email" TextBoxText="{Binding UpdateSourceTrigger=LostFocus, Path=NewFosterCarerInfo.partner_email, ValidatesOnDataErrors=true, NotifyOnValidationError=true}" TextBoxWidth="120" Margin="190,182,-61,0" VerticalAlignment="Top" Height="41" Width="321"/>
For anyone with my problem, here is the working code
UserControl xaml:
<UserControl x:Class="PIRS_Client.Control.LabelTextBox"
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="40.541" Width="321.027"
x:Name="Parent" Validation.ErrorTemplate="{x:Null}">
<Grid Height="41" VerticalAlignment="Top" HorizontalAlignment="Left" Width="321" DataContext="{Binding ElementName=Parent, ValidatesOnDataErrors=True}">
<StackPanel Orientation="Horizontal" Margin="0,8,50,9">
<Label Content="Label" Height="28" Name="BaseLabel" VerticalAlignment="Top" HorizontalContentAlignment="Right" Width="116" FontSize="11" />
<TextBox Height="22" Text="{Binding Path=TextBoxText, ValidatesOnDataErrors=True}" Width="100" Margin="0,0,0,0" x:Name="BaseTextBox" VerticalContentAlignment="Center" VerticalAlignment="Top" FontSize="11"/>
</StackPanel>
</Grid>
UserControl code behind:
public partial class LabelTextBox : UserControl, IDataErrorInfo
{
public LabelTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextBoxTextProperty =
DependencyProperty.Register(
"TextBoxText",
typeof(string),
typeof(LabelTextBox),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
#region IDataErrorInfo Members
public string Error
{
get
{
if (Validation.GetHasError(this))
return string.Join(Environment.NewLine, Validation.GetErrors(this).Select(e => e.ErrorContent));
return null;
}
}
public string this[string columnName]
{
get
{
// use a specific validation or ask for UserControl Validation Error
if (Validation.GetHasError(this))
{
var error = Validation.GetErrors(this).FirstOrDefault(e => ((BindingExpression)e.BindingInError).TargetProperty.Name == columnName);
if (error != null)
return error.ErrorContent as string;
}
return null;
}
}
#endregion
[Browsable(true)]
public string LabelText
{
get { return BaseLabel.Content.ToString(); }
set { BaseLabel.Content = value; }
}
[Browsable(true)]
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set {
SetValue(TextBoxTextProperty, value);
}
}
[Browsable(true)]
public double TextBoxWidth
{
get { return BaseTextBox.Width; }
set { BaseTextBox.Width = value; }
}
}
Using the UserControl:
<control:LabelTextBox HorizontalAlignment="Left" LabelText="Email" TextBoxText="{Binding Path=NewFosterCarerInfo.partner_email, ValidatesOnDataErrors=true}" TextBoxWidth="120" Margin="190,182,-61,0" VerticalAlignment="Top" Height="41" Width="321"/>
And in case you wanted a nice Validation.ErrorTemplate:
`<Style TargetType="{x:Type TextBox}">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,2,40,2" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
</TextBlock>
</Border>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>`
I have a self-made button that has image and text.
<ButtonImageApp:ButtonImage
BText="Button"
This works fine. But when I try to to do binding my button code is broken.
This wont work.
BText="{Binding Path=LocalizedResources.PlayButton, Source={StaticResource LocalizedStrings}}"
XAML
<Button x:Class="ButtonImageApp.ButtonImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
IsEnabledChanged="ButtonIsEnabledChanged"
MouseEnter="ButtonMouseEnter"
MouseLeave="ButtonMouseLeave">
<Grid>
<Image Stretch="None"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Row="0" Grid.Column="0"
x:Name="image" />
<TextBlock x:Name="txtButtonText"
Foreground="Black"
Text="{Binding Path=BText}"
Grid.Row="0" Grid.Column="0"
Margin="20,51,0,-51" TextAlignment="Center"></TextBlock>
</Grid>
</Button>
The Code:
public static readonly DependencyProperty ButtonText = DependencyProperty.Register("BText", typeof(string), typeof(ButtonImage), null);
public string BText
{
get { return (string)GetValue(ButtonText); }
set
{
SetValue(ButtonText, value);
txtButtonText.Text = value;
}
}
The problem is that when using binding the setter of the property is not called.
Instead you need to register for the dependency property changed:
public static readonly DependencyProperty ButtonText = DependencyProperty.Register("BText", typeof(string), typeof(ButtonImage), new PropertyMetadata(ButtonTextChanged));
private static void ButtonTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
txtButtonText.Text = e.NewValue;
}