In my application I have a TabControl. On one TabItem there are three TextBoxes where I can switch between them by pressing the Tab-Key.
Now I want to replace this standard-TextBoxes with custom-TextBoxes which should have a Null-Text that will be displayed if the Text is empty.
The XAML of my custom-TextBox is:
<UserControl x:Class="MyApplication.Controls.NullTextTextBox"
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:converter="clr-namespace:ScM.Converter"
mc:Ignorable="d" d:DesignHeight="24" d:DesignWidth="300"
x:Name="nullTextTextBox" IsHitTestVisible="True" Focusable="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" VerticalAlignment="Stretch" x:Name="tbInput"
Text="{Binding ElementName=nullTextTextBox,Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="{Binding ElementName=nullTextTextBox, Path=AcceptsReturn, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="{Binding ElementName=nullTextTextBox, Path=TextWrapping, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsTabStop="True" />
<TextBlock Grid.Column="0" VerticalAlignment="Top" Text="{Binding ElementName=nullTextTextBox,Path=NullText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left"
FontStyle="Italic" Foreground="DarkGray" Margin="4,4,0,0" IsHitTestVisible="False"
Visibility="{Binding ElementName=nullTextTextBox, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={converter:StringIsNullToVisibilityConverter}}"
Focusable="False"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Visibility>
<MultiBinding Converter="{converter:DeleteButtonMultiConverter}">
<Binding ElementName="nullTextTextBox" Path="IsClearButtonVisible" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
<Binding ElementName="nullTextTextBox" Path="Text" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBlock.Visibility>
<Hyperlink TextDecorations="{x:Null}" Command="{Binding ElementName=nullTextTextBox, Path=ClearTextCommand, Mode=OneWay}"
Focusable="False" >
<TextBlock FontFamily="Wingdings 2" Text="Î" Foreground="Red" FontWeight="Bold" FontSize="14" VerticalAlignment="Center" Margin="1,1,2,1"/>
</Hyperlink>
</TextBlock>
</Grid>
</UserControl>
I would say the code-behind of this xaml isn't relevant, because there are only DependencyProperties registered.
My default-TextBox behaves like I expect it. But if I press the Tab-Key while the focus is within one NullTextBox the focus is switched to the TabHeader and not to the second NullTextBox.
The xaml where the NullTextBoxes are located it looks like:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<controls:NullTextTextBox Grid.Row="0" NullText="Value 1"/>
<controls:NullTextTextBox Grid.Row="1" NullText="Value 2"/>
<controls:NullTextTextBox Grid.Row="2" NullText="Value 3"/>
</Grid>
Why does my second and third NullTextBox not get focused when I press the Tab-Key?
I've found out that if I remove the TextBlock which contains the Hyperlink the Tab-Order works as expected. But I need this TextBlock...
The code-behind of my custom textbox looks like:
public partial class NullTextTextBox : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof (string), typeof (NullTextTextBox), new PropertyMetadata(default(string)));
public static readonly DependencyProperty NullTextProperty = DependencyProperty.Register(
"NullText", typeof (string), typeof (NullTextTextBox), new PropertyMetadata(default(string)));
public static readonly DependencyProperty IsClearButtonVisibleProperty = DependencyProperty.Register(
"IsClearButtonVisible", typeof (bool), typeof (NullTextTextBox), new PropertyMetadata(default(bool)));
public static readonly DependencyProperty AcceptsReturnProperty = DependencyProperty.Register(
"AcceptsReturn", typeof (bool), typeof (NullTextTextBox), new PropertyMetadata(default(bool)));
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
"TextWrapping", typeof (TextWrapping), typeof (NullTextTextBox), new PropertyMetadata(default(TextWrapping)));
public TextWrapping TextWrapping
{
get { return (TextWrapping) GetValue(TextWrappingProperty); }
set
{
SetValue(TextWrappingProperty, value);
OnPropertyChanged();
}
}
private ICommand clearTextCommand;
public NullTextTextBox()
{
InitializeComponent();
IsClearButtonVisible = false;
Text = string.Empty;
NullText = "Enter text here...";
AcceptsReturn = false;
TextWrapping = TextWrapping.NoWrap;
}
public bool AcceptsReturn
{
get { return (bool) GetValue(AcceptsReturnProperty); }
set
{
SetValue(AcceptsReturnProperty, value);
OnPropertyChanged();
}
}
public ICommand ClearTextCommand
{
get { return clearTextCommand ?? (clearTextCommand = new RelayCommand<object>(p => ClearText())); }
}
public bool IsClearButtonVisible
{
get { return (bool) GetValue(IsClearButtonVisibleProperty); }
set
{
SetValue(IsClearButtonVisibleProperty, value);
OnPropertyChanged();
}
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set
{
SetValue(TextProperty, value);
OnPropertyChanged();
}
}
public string NullText
{
get { return (string) GetValue(NullTextProperty); }
set
{
SetValue(NullTextProperty, value);
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ClearText()
{
Text = string.Empty;
tbInput.Focus();
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and the converter used:
internal class DeleteButtonMultiConverter : MarkupExtension, IMultiValueConverter
{
private static DeleteButtonMultiConverter converter;
public DeleteButtonMultiConverter()
{
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null && values.Length == 2 && values[0] is bool && values[1] is string)
{
if ((bool) values[0] && !string.IsNullOrEmpty((string) values[1]))
return Visibility.Visible;
return Visibility.Collapsed;
}
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return converter ?? (converter = new DeleteButtonMultiConverter());
}
}
Change the TextBlock inside your Hyperlink for a Run, like this (note that, since Run doesn't support VerticalAlignment or Margin, I've either removed or moved those properties):
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="1,1,2,1">
<TextBlock.Visibility>
<MultiBinding Converter="{converter:DeleteButtonMultiConverter}">
<Binding ElementName="nullTextTextBox" Path="IsClearButtonVisible" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
<Binding ElementName="nullTextTextBox" Path="Text" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBlock.Visibility>
<Hyperlink TextDecorations="{x:Null}" Command="{Binding ElementName=nullTextTextBox, Path=ClearTextCommand, Mode=OneWay}"
Focusable="False" >
<Run FontFamily="Wingdings 2" Text="Î" Foreground="Red" FontWeight="Bold" FontSize="14" />
</Hyperlink>
</TextBlock>
Related
Not sure if what I want is possible, but I also wouldn't know why it isn't.
I have a user control with a dependency property (a string) which I define in XAML e.g. as follows:
<Window ... (EngraveUnitWindow)
DataContext = EngraveUnitViewModel
...
...
<parameters:DoubleParameterUserControl
DisplayName="Exchanger Offset [deg]"
DataContext="{Binding ExchangerOffset}"/>
The view model 'EngraveUnitViewModel' :
public class EngraveUnitViewModel : ViewModelBase, IUnitViewModel
...
...
public DoubleParameterViewModel ExchangerOffset { get; }
What I want to achieve, is set the value of DisplayName to ParameterName property in the DoubleParameterViewModel. So I created a Style which binds the DisplayName to the viewmodel as follows:
<UserControl.Resources>
<Style TargetType="parameters:DoubleParameterUserControl">
<Setter Property="DisplayName" Value="{Binding ParameterName, Mode=OneWayToSource}"/>
</Style>
</UserControl.Resources>
The complete DoubleParameterUserControl code below:
<UserControl
...
...
d:DataContext="{d:DesignInstance viewModels:DoubleParameterViewModel, d:IsDesignTimeCreatable=False}"
Margin="5">
<UserControl.Resources>
<Style TargetType="parameters:DoubleParameterUserControl">
<Setter Property="DisplayName" Value="{Binding ParameterName, Mode=OneWayToSource}"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=DoubleParameter, Path=DisplayName}" VerticalAlignment="Center" Margin="5,0,0,0" />
<Border Grid.Column="1" BorderThickness="1" BorderBrush="LightGray" Margin="0, 0, 5, 0">
<TextBlock
VerticalAlignment="Center"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Margin="5, 0, 5, 0">
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding ShowNumpadCommand}" />
</TextBlock.InputBindings>
</TextBlock>
</Border>
<Button x:Name="_button" Grid.Column="2" MinWidth="30" MinHeight="30" Content="..." Command="{Binding ShowNumpadCommand}"/>
</Grid>
</UserControl>
And its code behind (where I define the DependencyProp:
public partial class DoubleParameterUserControl
{
public string DisplayName
{
get => (string)GetValue(DisplayNameProperty);
set => SetValue(DisplayNameProperty, value);
}
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(DoubleParameterUserControl),
new PropertyMetadata(""));
public DoubleParameterUserControl()
{
InitializeComponent();
_button.Focus();
}
}
For completeness, the viewmodel:
public class DoubleParameterViewModel : ViewModelBase
{
private readonly Parameter<double> parameter;
private double value;
public RelayCommand ShowNumpadCommand { get; }
public string ParameterName { get; set; }
public double Value
{
get => parameter.Value;
set
{
parameter.Value = value;
Set(() => Value, ref this.value, value);
}
}
public DoubleParameterViewModel(Parameter<double> parameter)
{
this.parameter = parameter;
ShowNumpadCommand = new RelayCommand(ShowNumpad);
}
private void ShowNumpad()
{
var numpadViewModel = new VirtualKeypadsViewModel(true)
{
ParameterName = ParameterName,
Input = Value.ToString("F2", CultureInfo.InvariantCulture)
};
var numpad = new Numpad
{
Owner = System.Windows.Application.Current.MainWindow,
DataContext = numpadViewModel
};
if (numpad.ShowDialog() == true)
{
Value = numpadViewModel.ResultAsDouble();
}
}
}
Just to be clear, the property ParameterName in the ViewModel never gets set. So in my code, I want to popup a Numpad dialog which shows the parameter name in its title bar, but the ParameterName did not receive the bound DisplayName.
I hope somebody can explain me how I can solve that. (or, that it is not possible, and why not if that would sadly be the case)
It seems like DoubleParameterViewModel.ParameterName exists solely to provide a name when executing ShowNumpadCommand. If that's the case, forget the property and just pass DisplayName as your command parameter.
public ICommand ShowNumpadCommand { get; }
public DoubleParameterViewModel(Parameter<double> parameter)
{
this.parameter = parameter;
ShowNumpadCommand = new RelayCommand<string>(ShowNumpad);
}
private void ShowNumpad(string parameterName)
{
/* ... */
}
Get rid of the Style, and bind your button's command parameter to its owner's DisplayName:
<UserControl x:Name="EditorRoot">
<!-- ... -->
<Button x:Name="_button"
Command="{Binding ShowNumpadCommand}"
CommandParameter="{Binding ElementName=EditorRoot, Path=DisplayName}" />
<!-- ... -->
</UserControl>
I am currently implementing a ValidationRule to check if some invalid character are in a TextBox. I am happy that setting the class I have implemented that inherits ValidationRule on my TextBox sets it in red when such characters are found, but I would also like to use the Validation.HasError property or the Validation.Errors property to pop a messagebox telling the user that there are errors in the various textboxes in the page.
Is there a way to bind a property in my ViewModel to the Validation.HasError and/or to the Validation.Errors properties in order for me to have access to them in my ViewModel?
Here is my error style for the TextBox:
<Style x:Key="ErrorValidationTextBox" TargetType="{x:Type pres:OneTextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Red"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
<AdornedElementPlaceholder x:Name="MyAdorner"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is how I declare my TextBox (OneTextBox encapsulates the regular WPF TextBox) in my XAML:
<pres:OneTextBox Watermark="Name..." Margin="85,12,0,0" Style="{StaticResource ErrorValidationTextBox}"
AcceptsReturn="False" MaxLines="1" Height="22" VerticalAlignment="Top"
HorizontalAlignment="Left" Width="300" >
<pres:OneTextBox.Text>
<Binding Path="InterfaceSpecification.Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<interfaceSpecsModule:NoInvalidCharsRule/>
</Binding.ValidationRules>
</Binding>
</pres:OneTextBox.Text>
</pres:OneTextBox>
The Validation.HasError is readonly property, therefore Binding will not work with this property. This can be seen in ILSpy:
public virtual bool HasError
{
get
{
return this._validationError != null;
}
}
As an alternative, you should see a great article which provides a solution in the form of use attached dependency properties, there you will see a detailed explanation of the example.
Below is a full example from this article, I just translated it under C#, the original language is VB.NET:
XAML
<Window x:Class="HasErrorTestValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HasErrorTestValidation"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:TestData />
</Window.DataContext>
<StackPanel>
<TextBox x:Name="TestTextBox"
local:ProtocolSettingsLayout.MVVMHasError="{Binding Path=HasError}">
<TextBox.Text>
<Binding Path="TestText" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:OnlyNumbersValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock>
<TextBlock.Text>
<Binding Path="HasError" StringFormat="HasError is {0}"/>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<Binding Path="(Validation.HasError)" ElementName="TestTextBox" StringFormat="Validation.HasError is {0}"/>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
#region Model
public class TestData : INotifyPropertyChanged
{
private bool _hasError = false;
public bool HasError
{
get
{
return _hasError;
}
set
{
_hasError = value;
NotifyPropertyChanged("HasError");
}
}
private string _testText = "0";
public string TestText
{
get
{
return _testText;
}
set
{
_testText = value;
NotifyPropertyChanged("TestText");
}
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
#endregion
}
#endregion
#region ValidationRule
public class OnlyNumbersValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var result = new ValidationResult(true, null);
string NumberPattern = #"^[0-9-]+$";
Regex rgx = new Regex(NumberPattern);
if (rgx.IsMatch(value.ToString()) == false)
{
result = new ValidationResult(false, "Must be only numbers");
}
return result;
}
}
#endregion
public class ProtocolSettingsLayout
{
public static readonly DependencyProperty MVVMHasErrorProperty= DependencyProperty.RegisterAttached("MVVMHasError",
typeof(bool),
typeof(ProtocolSettingsLayout),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
CoerceMVVMHasError));
public static bool GetMVVMHasError(DependencyObject d)
{
return (bool)d.GetValue(MVVMHasErrorProperty);
}
public static void SetMVVMHasError(DependencyObject d, bool value)
{
d.SetValue(MVVMHasErrorProperty, value);
}
private static object CoerceMVVMHasError(DependencyObject d,Object baseValue)
{
bool ret = (bool)baseValue;
if (BindingOperations.IsDataBound(d,MVVMHasErrorProperty))
{
if (GetHasErrorDescriptor(d)==null)
{
DependencyPropertyDescriptor desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType());
desc.AddValueChanged(d,OnHasErrorChanged);
SetHasErrorDescriptor(d, desc);
ret = System.Windows.Controls.Validation.GetHasError(d);
}
}
else
{
if (GetHasErrorDescriptor(d)!=null)
{
DependencyPropertyDescriptor desc= GetHasErrorDescriptor(d);
desc.RemoveValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, null);
}
}
return ret;
}
private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor",
typeof(DependencyPropertyDescriptor),
typeof(ProtocolSettingsLayout));
private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
{
var ret = d.GetValue(HasErrorDescriptorProperty);
return ret as DependencyPropertyDescriptor;
}
private static void OnHasErrorChanged(object sender, EventArgs e)
{
DependencyObject d = sender as DependencyObject;
if (d != null)
{
d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty));
}
}
private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
{
var ret = d.GetValue(HasErrorDescriptorProperty);
d.SetValue(HasErrorDescriptorProperty, value);
}
}
As an alternative to the use of ValidationRule, in MVVM style you can try to implement IDataErrorInfo Interface. For more info see this:
Enforcing Complex Business Data Rules with WPF
all perfect work set NotifyOnValidationError="True" on binding;
(or maybe with binding group also possible)
then use
<Button IsEnabled="{Binding ElementName=tbPeriod, Path=(Validation.HasError)}"
sample with one textBox:
<val:RangeRulecan be changed to ms sample agerangerule etc
<TextBox MaxLength="5" x:Name="tbPeriod" HorizontalAlignment="Left" VerticalAlignment="Top" Width="162" Margin="10,10,0,0" Style="{StaticResource TextBoxInError}">
<TextBox.Text>
<Binding Path="ReportPeriod" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<val:RangeRule Min="70" Max="5000" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
In response to Anatoliy's request for an example of a non-working project:
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestAttachedPropertyValidationError">
<Style TargetType="{x:Type local:TextBoxCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TextBoxCustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Grid.Resources>
<Label
Grid.Row ="0"
Grid.Column="0"
Content="Enter a numeric value:" />
<TextBox
Grid.Row ="0"
Grid.Column="2"
local:HasErrorUtility.HasError="{Binding NumericPropHasError, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Text="{Binding NumericProp, Mode=TwoWay, UpdateSourceTrigger=LostFocus, RelativeSource={RelativeSource TemplatedParent}}" />
<Label
Grid.Row ="1"
Grid.Column="0"
Content="Value entered:" />
<Label
Grid.Row ="1"
Grid.Column="2"
Content="{TemplateBinding NumericProp}" />
<Label
Grid.Row ="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Visibility="{TemplateBinding NumericPropHasError, Converter={StaticResource BooleanToVisibilityConverter}}"
Foreground="Red"
Content="Not a numeric value" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TextBoxCustomControl.cs
using System.Windows;
using System.Windows.Controls;
namespace TestAttachedPropertyValidationError
{
public class TextBoxCustomControl : Control
{
static TextBoxCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxCustomControl), new FrameworkPropertyMetadata(typeof(TextBoxCustomControl)));
}
public static readonly DependencyProperty NumericPropProperty =
DependencyProperty.Register("NumericProp", typeof (int), typeof (TextBoxCustomControl), new PropertyMetadata(default(int)));
public int NumericProp
{
get { return (int) GetValue(NumericPropProperty); }
set { SetValue(NumericPropProperty, value); }
}
public static readonly DependencyProperty NumericPropHasErrorProperty =
DependencyProperty.Register("NumericPropHasError", typeof (bool), typeof (TextBoxCustomControl), new PropertyMetadata(default(bool)));
public bool NumericPropHasError
{
get { return (bool) GetValue(NumericPropHasErrorProperty); }
set { SetValue(NumericPropHasErrorProperty, value); }
}
}
}
HasErrorUtility.cs
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace TestAttachedPropertyValidationError
{
class HasErrorUtility
{
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached("HasError",
typeof(bool),
typeof(HasErrorUtility),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
null,
CoerceHasError));
public static bool GetHasError(DependencyObject d)
{
return (bool)d.GetValue(HasErrorProperty);
}
public static void SetHasError(DependencyObject d, bool value)
{
d.SetValue(HasErrorProperty, value);
}
private static object CoerceHasError(DependencyObject d, Object baseValue)
{
var ret = (bool)baseValue;
if (BindingOperations.IsDataBound(d, HasErrorProperty))
{
if (GetHasErrorDescriptor(d) == null)
{
var desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType());
desc.AddValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, desc);
ret = Validation.GetHasError(d);
}
}
else
{
if (GetHasErrorDescriptor(d) != null)
{
var desc = GetHasErrorDescriptor(d);
desc.RemoveValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, null);
}
}
return ret;
}
private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor",
typeof(DependencyPropertyDescriptor),
typeof(HasErrorUtility));
private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
{
var ret = d.GetValue(HasErrorDescriptorProperty);
return ret as DependencyPropertyDescriptor;
}
private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
{
d.SetValue(HasErrorDescriptorProperty, value);
}
private static void OnHasErrorChanged(object sender, EventArgs e)
{
var d = sender as DependencyObject;
if (d != null)
{
d.SetValue(HasErrorProperty, d.GetValue(Validation.HasErrorProperty));
}
}
}
}
ViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TestAttachedPropertyValidationError
{
public class ViewModel :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _vmNumericProp;
private bool _vmNumericPropHasError;
public int VmNumericProp
{
get { return _vmNumericProp; }
set
{
_vmNumericProp = value;
OnPropertyChanged();
}
}
public bool VmNumericPropHasError
{
get { return _vmNumericPropHasError; }
set
{
_vmNumericPropHasError = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainWindow.xaml
<Window x:Class="TestAttachedPropertyValidationError.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestAttachedPropertyValidationError"
Title="MainWindow" Height="350" Width="525">
<StackPanel Margin="10">
<StackPanel.Resources>
<local:ViewModel x:Key="VM1"/>
<local:ViewModel x:Key="VM2"/>
</StackPanel.Resources>
<Label Content="Custom Control...}"></Label>
<local:TextBoxCustomControl
Margin="10"
DataContext="{StaticResource VM1}"
NumericProp="{Binding VmNumericProp}"
NumericPropHasError="{Binding VmNumericPropHasError}"/>
<Label Content="Regular XAML...}" Margin="0,20,0,0"/>
<Grid
Margin="10"
DataContext="{StaticResource VM2}"
>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Grid.Resources>
<Label
Grid.Row ="0"
Grid.Column="0"
Content="Enter a numeric value:" />
<TextBox
Grid.Row ="0"
Grid.Column="2"
local:HasErrorUtility.HasError="{Binding VmNumericPropHasError, Mode=TwoWay}"
Text="{Binding VmNumericProp, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
<Label
Grid.Row ="1"
Grid.Column="0"
Content="Value entered:" />
<Label
Grid.Row ="1"
Grid.Column="2"
Content="{Binding VmNumericProp}" />
<Label
Grid.Row ="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Visibility="{Binding VmNumericPropHasError, Converter={StaticResource BooleanToVisibilityConverter}}"
Foreground="Red"
Content="Not a numeric value" />
</Grid>
</StackPanel>
I have created several UserControls like a HeaderedDatePicker.
The XAML of this UserControl looks like:
<UserControl x:Class="Book.CustomControls.HeaderedDatePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Width="200" Height="50">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="2,2" VerticalAlignment="Center" Text="{Binding Header}" FontWeight="{Binding HeaderFontWeight}"/>
<DatePicker Grid.Row="1" Margin="2,2" VerticalAlignment="Center" SelectedDate="{Binding Date, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
The Code-Behind with the registered DependencyPropertys is:
public partial class HeaderedDatePicker : UserControl
{
public HeaderedDatePicker()
{
this.InitializeComponent();
this.HeaderFontWeight = FontWeights.Normal;
this.Date = DateTime.Now;
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(HeaderedDatePicker));
public static readonly DependencyProperty HeaderFontWeightProperty = DependencyProperty.Register("HeaderFontWeight", typeof(FontWeight), typeof(HeaderedDatePicker));
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date", typeof(DateTime), typeof(HeaderedDatePicker));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public FontWeight HeaderFontWeight
{
get { return (FontWeight)GetValue(HeaderFontWeightProperty); }
set { SetValue(HeaderFontWeightProperty, value); }
}
public DateTime Date
{
get { return (DateTime)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
}
Everything is working fine. Now I want to create another HeaderedControl like a HeaderedComboBox or so. My question now is:
Do I have to write the HeaderProperty and HeaderFontWeightProperty in each Code-Behind-File or is there a way to do this in a base-class?
I tried to create a base-class where the properties are registered, but in the code-behind of the HeaderedDatePicker I couldn't inherit from my class
public class main : UserControl
{
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(main));
public static readonly DependencyProperty HeaderFontWeightProperty = DependencyProperty.Register("HeaderFontWeight", typeof(FontWeight), typeof(main));
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date", typeof(DateTime), typeof(main));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public FontWeight HeaderFontWeight
{
get { return (FontWeight)GetValue(HeaderFontWeightProperty); }
set { SetValue(HeaderFontWeightProperty, value); }
}
public DateTime Date
{
get { return (DateTime)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
}
Then in every UserControl you are supposed to derive from previously declared main.
public partial class HeaderedComboBox : main
{
public HeaderedComboBox()
{
this.InitializeComponent();
this.DataContext = this;
}
}
Finally
<local:main x:Class="WpfApplication5.HeaderedComboBox"
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:WpfApplication5"
mc:Ignorable="d"
Width="200" Height="50">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="2,2" VerticalAlignment="Center" Text="{Binding Header}" FontWeight="{Binding HeaderFontWeight}"/>
<ComboBox Grid.Row="1" Margin="2,2" VerticalAlignment="Center" ItemsSource="{Binding ItemsSource, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Let me know whether it works.
You can register attached property. See this link. Attached property can be used for any dependency object.
I'm having troubles creating a custom control with a ComboBox.
This is my simple code:
public class MyComboBox : Control
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MyComboBox), new UIPropertyMetadata(null));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
// Using a DependencyProperty as the backing store for DisplayMemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(MyComboBox), new UIPropertyMetadata(""));
public string SelectedValuePath
{
get { return (string)GetValue(SelectedValuePathProperty); }
set { SetValue(SelectedValuePathProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValuePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(MyComboBox), new UIPropertyMetadata(""));
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(MyComboBox), new UIPropertyMetadata(null));
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedIndex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(MyComboBox), new UIPropertyMetadata(0));
static MyComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyComboBox), new FrameworkPropertyMetadata(typeof(MyComboBox)));
}
}
and this is its Generic.xaml:
<Style TargetType="{x:Type local:MyComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyComboBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="MyComboBox" />
<ComboBox Grid.Column="1"
ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource Mode=TemplatedParent}}"
SelectedIndex="{Binding Path=SelectedIndex, RelativeSource={RelativeSource Mode=TemplatedParent}}"
DisplayMemberPath="{Binding Path=DisplayMemberPath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
SelectedValuePath="{Binding Path=SelectedValuePath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
SelectedValue="{Binding Path=SelectedValue, RelativeSource={RelativeSource Mode=TemplatedParent}}">
</ComboBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
To test it, I made a simple WPF application with this MainWindow.xaml:
<Window x:Class="Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Example"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" Margin="4" VerticalAlignment="Center"
ItemsSource="{Binding Path=Numbers}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding Path=Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="0" Grid.Column="1" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<local:MyComboBox Grid.Row="1" Grid.Column="0" Margin="4" VerticalAlignment="Center"
ItemsSource="{Binding Path=MyNumbers}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1" Grid.Column="1" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
and this ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged("Number");
}
}
public Dictionary<string, int> Numbers { get; set; }
private int _myNumber;
public int MyNumber
{
get { return _myNumber; }
set
{
_myNumber = value;
OnPropertyChanged("MyNumber");
}
}
public Dictionary<string, int> MyNumbers { get; set; }
public ViewModel()
{
Numbers = new Dictionary<string, int>()
{
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
};
Number = 1;
MyNumbers = new Dictionary<string, int>()
{
{ "Four", 4 },
{ "Five", 5 },
{ "Six", 6 }
};
MyNumber = 4;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(name));
}
}
}
When I start it, my custom control has a red border and the output Window of Visual Studio signal this error:
System.Windows.Data Error: 23 : Cannot convert '[Four, 4]' from type 'KeyValuePair`2' to type 'System.Int32' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: Int32Converter cannot convert from System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].
at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
at System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'
System.Windows.Data Error: 7 : ConvertBack cannot convert value '[Four, 4]' (type 'KeyValuePair`2'). BindingExpression:Path=MyNumber; DataItem='ViewModel' (HashCode=55591935); target element is 'MyComboBox' (Name=''); target property is 'SelectedValue' (type 'Object') NotSupportedException:'System.NotSupportedException: Int32Converter cannot convert from System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)
at MS.Internal.Data.ObjectTargetConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture)
at System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)'
The problem disappear when I remove the property SelectedIndex from the Generic.xaml, but I need it because I want that who will use my control, can have the same basic functionality of a ComboBox.
Anyone knows how to solve it?
I find the solution by myself.
The problem is on the default value I inserted for SelectedIndex: it must be -1 and not 0.
At first it seemed to me your problem is in SelectedValue. In you VM it has type of int, but WPF expects it to be KeyValuePair. The type of the collection's item.
ItemsSource="{Binding Path=MyNumbers}" // KeyValuePair
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" // int
Then I realized my mistake that SelectedItem has to be of KeyValuePair type.
Error message looks as WPF does not look through the items' property given by the SelectedValuePath but tries to conver it explicitly to KeyValue pair. That is not documented behavior.
So I'm playing around with some WPF UserControls and DependencyProperties.
I've got the following UserControl:
<UserControl x:Class="ValueInser.UserControls.FirmPersonInsert"
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:self="clr-namespace:ValueInser.UserControls"
x:Name="uc1"
mc:Ignorable="d"
d:DesignHeight="225" d:DesignWidth="450"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100px"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition IsEnabled="{Binding ElementName=rbtnPers, Path=IsChecked}"/>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<GroupBox Grid.Column="1" Header="Privat-/Juristische Person" Width="150" HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal">
<RadioButton Content="Person" IsChecked="{Binding Path=IsCompany}"/>
<Line Height="1" Width="25" />
<RadioButton Content="Firma" Name="rbtnCompany" />
</StackPanel>
</GroupBox>
<Label Content="Firma" Grid.Column="0" Grid.Row="1" />
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Path=Company}" IsEnabled="{Binding ElementName=rbtnCompany, Path=IsChecked}"/>
<Label Content="Anrede" Grid.Column="0" Grid.Row="2" />
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Path=Salutation}" />
<Label Content="Name" Grid.Column="0" Grid.Row="3" />
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Path=LastName, RelativeSource={RelativeSource self}}" />
<Label Content="Vorname" Grid.Column="0" Grid.Row="4" />
<TextBox Grid.Column="1" Grid.Row="4" Text="{Binding Path=FirstName}"/>
<Label Content="Strasse" Grid.Column="0" Grid.Row="5" />
<TextBox Grid.Column="1" Grid.Row="5" Text="{Binding Path=Street}" />
<Label Content="Ort" Grid.Column="0" Grid.Row="6" />
<TextBox Grid.Column="1" Grid.Row="6" Text="{Binding Path=City}" />
<Label Content="PLZ" Grid.Column="0" Grid.Row="7" />
<TextBox Grid.Column="1" Grid.Row="7" Text="{Binding Path=ZIP}" />
</Grid>
With this Code Behind:
#region Dependency Properties
public static readonly DependencyProperty _company = DependencyProperty.Register("Company", typeof(string), typeof(UserControl));
public string Company
{
get
{
return this.GetValue(_company) as string;
}
set
{
this.SetValue(_company, value);
}
}
public static readonly DependencyProperty _salutation = DependencyProperty.Register("Salutation", typeof(string), typeof(UserControl));
public string Salutation
{
get
{
return this.GetValue(_salutation) as string;
}
set
{
this.SetValue(_salutation, value);
}
}
public static readonly DependencyProperty _lastName = DependencyProperty.Register("LastName", typeof(string), typeof(UserControl));
public string LastName
{
get
{
return this.GetValue(_lastName) as string;
}
set
{
this.SetValue(_lastName, value);
}
}
public static readonly DependencyProperty _firstName = DependencyProperty.Register("FirstName", typeof(string), typeof(UserControl));
public string FirstName
{
get
{
return this.GetValue(_firstName) as string;
}
set
{
this.SetValue(_firstName, value);
}
}
public static readonly DependencyProperty _street = DependencyProperty.Register("Street", typeof(string), typeof(UserControl));
public string Street
{
get
{
return this.GetValue(_street) as string;
}
set
{
this.SetValue(_street, value);
}
}
public static readonly DependencyProperty _city = DependencyProperty.Register("City", typeof(string), typeof(UserControl));
public string City
{
get
{
return this.GetValue(_city) as string;
}
set
{
this.SetValue(_city, value);
}
}
public static readonly DependencyProperty _zip = DependencyProperty.Register("ZIP", typeof(string), typeof(UserControl));
public string ZIP
{
get
{
return this.GetValue(_zip) as string;
}
set
{
this.SetValue(_zip, value);
}
}
public static readonly DependencyProperty _isCompany = DependencyProperty.Register("IsCompany", typeof(bool), typeof(UserControl));
public bool IsCompany
{
get
{
return !(bool)this.GetValue(_isCompany);
}
set
{
this.SetValue(_isCompany, value);
}
}
#endregion
And now the tricky part comes to play.
So that I can bind the textboxs text property to the dependencyproperties in the code behind I have to set the datacontext to itself.
When I want to use this control now on a Window I have a problem:
<uc:FirmPersonInsert DataContext="{DynamicResource ResourceKey=mvm}"
IsCompany="{Binding Path=ProjectArchitect.IsCompany}"
Company="{Binding Path=ProjectArchitect.Company}"
LastName="{Binding Path=ProjectArchitect.LastName}"
FirstName="{Binding Path=ProjectArchitect.FirstName}"
Street="{Binding Path=ProjectArchitect.Street}"
City="{Binding Path=ProjectArchitect.City}"
ZIP="{Binding Path=ProjectArchitect.ZIP}"/>
This Properties are stored on the ViewModel of the Application. But now the whole construct starts to get weired.
I can't change the datacontext because otherwise the bindings on the usercontrol won't work anymore.
I think I missunderstand something completly how all of that should work.
I need the dependencyprops so that a user of my control can simply bind to the new "Property Names" like LastName.
Please can someone explain me, where am I thinking wrong ?
Remove the DataContext="{Binding RelativeSource={RelativeSource Self}}"> from the usercontrol and instead:
<Grid DataContext="{Binding ElementName=uc1}">.
Also remove all the RelativeSources from the individual textboxes.