I'm trying to create a WPF textbox style that incorporates PropertyStatus. This would have the benefit of not having to code the propertystatus control after every textbox on every form. I am unable to get it to work. I'm having difficulty with the template binding (I think). Here is my style source: <!-- cslatextbox style --> <Style x:Key="CslaTextBoxStyle" TargetType="{x:Type TextBox}"> <Setter Property="Margin" Value="0,1,0,1"/> <Setter Property="Validation.ErrorTemplate"> <Setter.Value> <ControlTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <AdornedElementPlaceholder Grid.Column="0" /> <csla:PropertyStatus Grid.Column="1" Property="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text}" TargetControl="{Binding RelativeSource={RelativeSource TemplatedParent}}"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsReadOnly" Value="true"> <Setter Property="Background" Value="lightyellow"/> </Trigger> </Style.Triggers> </Style> Here is the typical textbox Xaml: <TextBox Grid.Column="1" Grid.Row="0" Name="CodeTextBox" Text="{Binding Code, Converter={StaticResource IdentityConverter}, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource CslaTextBoxStyle}"/> Can anyone suggest a solution or explain why this can't be done. Thanks Russ
First off, I know so little about all of this (I'm just learning WPF) that anything I say is probably wrong. But that's never stopped me before!
I think your PropertyStatus' Property binding should use TemplateBinding, not Binding. The way you have it now, it's bound to the contents of the Text property, not the Text binding source.
But I still can't figure out how to get the TargetControl binding to work correctly.
Dan
I was assuming there was an error in the TargetControl binding, because VS said it was missing a Path. Turns out this is a known bug, and can be ignored, or you can fix it by adding "Path=.".
Dan
Thanks for the ideas dagware. Unfortunately I am still unable to find the correct syntax to get this to work.
I have given up on using a style and I am now attempting to create a CslaTextBox UserControl. The goal is to replace several thousand of these:
<TextBlock Grid.Column="0" Grid.Row="0" Name="CodeTextBlock" Text="Code:" Style="{StaticResource TextBlockStyle}" />
<TextBox Grid.Column="1" Grid.Row="0" Name="CodeTextBox"
Text="{Binding Code, Converter={StaticResource IdentityConverter}, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBoxStyle}"
IsReadOnly="{Binding ElementName=CodePropertyStatus, Path=CanWrite, Converter={StaticResource invertBoolConverter}}"/>
<csla:PropertyStatus Grid.Column="2" Grid.Row="0" x:Name="CodePropertyStatus" Property="{Binding Path=Code}"/>
with these:
<TextBlock Grid.Column="0" Grid.Row="0" Name="CodeTextBlock" Text="Code:" Style="{StaticResource TextBlockStyle}" />
<controls:CslaTextBox Grid.Column="1" Grid.Row="0" x:Name="CodeTextBox" Text="{Binding Code}" />
The CslaTextBox UserControl will simply have a TextBox and a PropertyStatus control. Here is the XAML:
<UserControl x:Class="LossTrack.Controls.CslaTextBox" x:Name="cslaTextBox"
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:csla="clr-namespace:Csla.Xaml;assembly=Csla.Xaml"
mc:Ignorable="d"
d:DesignHeight="23" d:DesignWidth="150">
<UserControl.Resources>
<csla:IdentityConverter x:Key="IdentityConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" x:Name="textBox" IsEnabled="{Binding ElementName=propertyStatus, Path=CanWrite}" Margin="0,1,2,1"
Text="{Binding ElementName=cslaTextBox, Path=Text, Converter={StaticResource IdentityConverter}, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
<csla:PropertyStatus Grid.Column="1" x:Name="propertyStatus"
Property="{Binding ElementName=cslaTextBox, Path=Text}"/>
</Grid>
</UserControl>
Here is the CslaUserControl code behind:
using System;
using System.Windows;
using System.Windows.Controls;
namespace LossTrack.Controls
{
public partial class CslaTextBox : UserControl
{
public CslaTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string), typeof(CslaTextBox),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(textChangedCallBack)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
CslaTextBox cslaTextBox = (CslaTextBox)property;
cslaTextBox.textBox.Text = (string)args.NewValue;
}
}
}
The TextBox Text binding appears to operate correctly in all respects. The problem is getting the correct XAML syntax for the clsa:PropertyStatus Property property.
Does anyone have any suggestions?
Thanks in advance
Russ.
PropertyStatus is a little special, in that it needs to get a reference to the actual underlying business object so it can add a handler for the object's PropertyChanged event. To do this, it takes the binding expression and uses it to get the underlying source object.
My guess is that what's happening here is that the underlying source object is the usercontrol, not the business object - but without using the debugger to walk through the PropertyStatus code it is hard to say for sure. That level of data binding infrastructure gets somewhat complex.
There is a GetRealSource() method in PropertyStatus that walks back through the dot notation of the Path to find the source. I'm guessing that code doesn't handle whatever is different about being inside a usercontrol.
If anyone has figured this out, I'm trying to do the same thing, and having similar issues. It would make things so much simpler (and cleaner) if this could work.
OK, so I think I figured out a way to do it (haven't cleaned it up yet, but it appears to work). Basically stole the technique from PropertyStatus.
The key part here is the SetSource method, which Binds the PropertyStatus.Property to the BOTextBox.Property binding - which then allows PropertyStatus to do it's thing.
The XAML:
<UserControl x:Class="JefBar.Core.WPF.BOTextBox"
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:corewpf="clr-namespace:JefBar.Core.WPF"
xmlns:csla="clr-namespace:Csla.Xaml;assembly=Csla.Xaml"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="boTextBox">
<StackPanel
Orientation="Horizontal"
>
<TextBox
Name="patientNumber"
Text="{Binding ElementName=boTextBox, Path=Property, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"
IsEnabled="{Binding ElementName=propertyStatus, Path=CanWrite}"
Visibility="{Binding ElementName=propertyStatus, Path=CanRead, Converter={corewpf:BooleanToVisibilityConverter}}"
MinWidth="75"
/>
<csla:PropertyStatus
x:Name="propertyStatus"
/>
</StackPanel>
</UserControl>
The Code Behind (interesting part):
using System.Windows;
using System.Windows.Data;
using Csla.Xaml;
namespace JefBar.Core.WPF
{
/// <summary>
/// Interaction logic for BOTextBox.xaml
/// </summary>
public partial class BOTextBox
{
/// <summary>
/// Gets or sets the source business
/// property to which this control is bound.
/// </summary>
public static readonly DependencyProperty PropertyProperty = DependencyProperty.Register(
"Property",
typeof(object),
typeof(BOTextBox),
new PropertyMetadata(new object(), (o, e) => ((BOTextBox) o).SetSource())
);
/// <summary>
/// Gets or sets the source business
/// property to which this control is bound.
/// </summary>
public object Property
{
get { return GetValue(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
/// <summary>
/// Sets the source binding and updates status.
/// </summary>
private BindingExpression oldBinding;
protected virtual void SetSource()
{
var binding = GetBindingBLOCKED EXPRESSION;
}
}
}
This looks interesting - I'd like to see the full code - but it seems it hasn't come out properly. (See the BLOCKED EXPRESSION at the end).
Here is the source as an attachment, updated quite a bit since the original post (various templates and properties to control behavior).
Copyright (c) Marimer LLC