My requirement is Binding dynamic property to my Value (double) property of Custom control.But it not working as expected.
Below is the code :
Simple customcontrol part :
xaml
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox x:Name="PART_TextBox" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
c#
public class CustomControl1 : Control
{
public double? Value
{
get { return (double?)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double?), typeof(CustomControl1), new PropertyMetadata(null, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValueChange)));
private static object CoerceValueChange(DependencyObject d, object baseValue)
{
return baseValue;
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((CustomControl1)d).OnValueChanged(e);
}
private void OnValueChanged(DependencyPropertyChangedEventArgs e)
{
if (tb != null && e.NewValue != null)
tb.Text = e.NewValue.ToString();
else
tb.Text = string.Empty;
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
TextBox tb = null;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
tb = base.GetTemplateChild("PART_TextBox") as TextBox;
}
}
here goes the usage :
xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Label Content="Double TextBox without Converter" />
<Label Content="Double TextBox with Converter" />
<Label Content="MS TextBox" />
<Button Content="Button" Click="Button_Click_1"/>
</StackPanel>
<StackPanel Grid.Column="1">
<cc:CustomControl1 Value="{Binding MyValue}" Height="40" Width="200" Background="Red"/>
<TextBox Height="30" Text="{Binding MyValue}" />
</StackPanel>
</Grid>
c#
public partial class MainWindow : Window, INotifyPropertyChanged
{
private dynamic myValue;
public dynamic MyValue
{
get { return myValue; }
set { myValue = value; RaisePropertyChanged("MyValue"); }
}
public MainWindow()
{
InitializeComponent();
this.myValue = 3;
this.DataContext = this;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.myValue = 5;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
the coerce's base value is always null,but if i use converter in binding it works as expected.
but i want to use like normal property , Please help me on this.
binding works perfectly if i use TextBox,it is not the issue ,, i'm facing issue while i use DP's in CustomControl
Regards,
Kumar
Found answer from here..............
http://social.msdn.microsoft.com/Forums/vstudio/en-US/7d0f0a90-b0b8-45c9-9ec9-76517c8ea4af/dynamic-property-not-working-well-with-dependencyproperties?forum=wpf
Related
I am trying to figure out how I can set the "IsEnabled" property to false on a Button but then have the children (in my case a Combobox) be enabled. It seems the children inherit the property value from the parent element (in this case the disabled button) and therefore also disables itself. How can I prevent this?
I have tried to lookup answers but they all use "Override Metadata" which WinUI3 and Windows App SDK do not contain.
<Button
x:Name="Button1"
IsEnabled="False">
<Button.Content>
<ComboBox x:Name="ComboBox1" />
</Button.Content>
</Button>
You can create a custom control like this:
Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Buttons">
<Style TargetType="local:CustomButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomButton">
<Grid
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Button
x:Name="ButtonControl"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsEnabled="{TemplateBinding IsButtonEnabled}" />
<ContentControl
x:Name="ContentControl"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
IsEnabled="{TemplateBinding IsContentEnabled}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomButton.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Buttons;
[TemplatePart(Name = nameof(ButtonControl), Type = typeof(Button))]
[TemplatePart(Name = nameof(ContentControl), Type = typeof(ContentControl))]
public sealed class CustomButton : ContentControl
{
public static readonly DependencyProperty IsButtonEnabledProperty = DependencyProperty.Register(
nameof(IsButtonEnabled),
typeof(bool),
typeof(CustomButton),
new PropertyMetadata(true));
public static readonly DependencyProperty IsContentEnabledProperty = DependencyProperty.Register(
nameof(IsContentEnabled),
typeof(bool),
typeof(CustomButton),
new PropertyMetadata(true));
public CustomButton()
{
this.DefaultStyleKey = typeof(CustomButton);
}
public bool IsButtonEnabled
{
get => (bool)GetValue(IsButtonEnabledProperty);
set => SetValue(IsButtonEnabledProperty, value);
}
public bool IsContentEnabled
{
get => (bool)GetValue(IsContentEnabledProperty);
set => SetValue(IsContentEnabledProperty, value);
}
private Button? ButtonControl { get; set; }
private ContentControl? ContentControl { get; set; }
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
ButtonControl = GetTemplateChild(nameof(ButtonControl)) as Button;
if (GetTemplateChild(nameof(ContentControl)) is ContentControl contentControl)
{
if (ContentControl is not null)
{
ContentControl.SizeChanged -= ContentControl_SizeChanged;
}
ContentControl = contentControl;
ContentControl.SizeChanged += ContentControl_SizeChanged;
}
}
private void ContentControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is ContentControl contentControl &&
ButtonControl is not null)
{
ButtonControl.Width = contentControl.ActualWidth + 20.0;
ButtonControl.Height = contentControl.ActualHeight + 20.0;
}
}
}
And use it like this:
MainWindow.xaml
<Grid>
<local:CustomButton
IsButtonEnabled="False"
IsContentEnabled="True">
<ComboBox
SelectedIndex="0"
SelectionChanged="ComboBox_SelectionChanged">
<x:String>Blue</x:String>
<x:String>Green</x:String>
<x:String>Red</x:String>
</ComboBox>
</local:CustomButton>
</Grid>
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 wanted to created data bindable property inside a user control. And this user control contains inside a "Windows Runtime Component" project. I used below code to create property.
public MyItem CurrentItem
{
get { return (MyItem)GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentItem.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register("CurrentItem", typeof(MyItem), typeof(CollapseUserControl), new PropertyMetadata(null));
When I compile the project I get below error.
Type 'HierachyLib.CollapseUserControl' contains externally visible field 'HierachyLib.CollapseUserControl.CurrentItemProperty'. Fields can be exposed only by structures.
Update 1 - Source code for whole class
public sealed partial class CollapseUserControl : UserControl, IHierarchyHeightFix
{
public MyItem CurrentItem
{
get { return (MyItem)GetValue(CurrentItemProperty); }
set { SetValue(CurrentItemProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentItemProperty =
DependencyProperty.Register("CurrentItem", typeof(MyItem), typeof(CollapseUserControl), new PropertyMetadata(null));
Boolean viewState = true;
public CollapseUserControl()
{
this.DataContext = CurrentItem;
this.InitializeComponent();
this.Loaded += CollapseUserControl_Loaded;
}
void CollapseUserControl_Loaded(object sender, RoutedEventArgs e)
{
LoadData();
if (this.Height.Equals(double.NaN))
{
this.Height = 50;
}
//this.Height = 50;
//this.Width = double.NaN;
}
private void LoadData()
{
if (CurrentItem != null)
{
if (CurrentItem.IsValueControl)
{
ChildItemContainer.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
ValueItem.Visibility = Windows.UI.Xaml.Visibility.Visible;
ValueItem.Text = CurrentItem.Value;
}
else
{
ChildItemContainer.Visibility = Windows.UI.Xaml.Visibility.Visible;
ValueItem.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
ChildItems.ItemsSource = CurrentItem.Childs;
//foreach (MyItem item in CurrentItem.Childs)
//{
// CollapseUserControl control = new CollapseUserControl();
// control.CurrentItem = item;
// ChildItems.Items.Add(control);
//}
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (viewState)
{
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Visible;
//show.Begin();
}
else
{
//hide.Begin();
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
viewState = !viewState;
}
private void hide_Completed_1(object sender, object e)
{
ChildItems.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
private void show_Completed_1(object sender, object e)
{
}
}
Update 2 : XAML Code
<UserControl
x:Class="HierachyLib.CollapseUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HierachyLib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<DataTemplate x:Key="ReviewsItemsTemplate">
<StackPanel Margin="0,0,0,20">
<TextBlock Text="TEST" />
<TextBlock Text="TEST"/>
</StackPanel>
</DataTemplate>
<ItemsPanelTemplate x:Key="ReviewsItemsPanelTemplate">
<StackPanel Margin="0,0,0,0" Width="Auto"/>
</ItemsPanelTemplate>
</UserControl.Resources>
<!--xmlns:my="clr-namespace:HierarchyCollapse"-->
<Grid>
<Grid Name="ChildItemContainer">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Margin="0,0,70,0" Fill="Transparent" Canvas.ZIndex="4"/>
<ListView HorizontalContentAlignment="Stretch" Background="#FF6599CD" CanDragItems="False" CanReorderItems="False" Grid.Row="0"
ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollMode="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollMode="Disabled">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Height="40">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<TextBlock Text="Sample Text" FontSize="17" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" Canvas.ZIndex="10"/>
<Image PointerPressed="Button_Click_1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Source="Assets/arrw_right.png" Stretch="None" Margin="0,0,10,0"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListViewItem />
</ListView>
<ListView Grid.Row="1"
IsHitTestVisible="True"
CanDragItems="True"
CanReorderItems="True"
AllowDrop="True"
Name="ChildItems"
SelectionMode="None"
IsItemClickEnabled="False">
<ListView.ItemTemplate>
<DataTemplate>
<local:CollapseUserControl CurrentItem="{Binding RelativeSource={RelativeSource Self}}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
<TextBlock Name="ValueItem" Margin="0,0,0,0" Height="40" FontSize="36"></TextBlock>
</Grid>
New error I get:
Failed to create a 'Windows.UI.Xaml.PropertyPath' from the text ''.
Error comes from <local:CollapseUserControl CurrentItem="{Binding RelativeSource={RelativeSource Self}}" />.
It seems like you can mark your property as internal and then it starts working...
internal static readonly DependencyProperty CurrentItemProperty...
EDIT*
A better approach that seems to be what the platform controls do is to have an actual CLR property that exposes the DependencyProperty object - something like this:
private static readonly DependencyProperty CurrentItemPropertyField =
DependencyProperty.Register/RegisterAttached(...);
internal static DependencyProperty CurrentItemProperty
{
get
{
return CurrentItemPropertyField;
}
}
This allows tools such as Blend to discover the properties.
You can use an automatic property setting the default value (from c# 5) like this:
public static DependencyProperty CurrentItemPropertyField { get; } =
DependencyProperty.Register("Value", typeof(string), typeof(MyControl), new PropertyMetadata("{No Value}"));
Works also for UWP applications
I am trying to build a UserControl but I am having trouble getting my bindings to work. I know I am missing something, but I can't figure out what it is. I am not getting any BindingExpressions
XAML
<UserControl x:Class="WpfApplication3.NumericUpDown"
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:WpfApplication3">
<Grid>
<StackPanel>
<TextBox Text="{Binding NumericUpDownText, Mode=TwoWay, RelativeSource={RelativeSource AncestorType={x:Type local:NumericUpDown}}}" LostFocus="PART_NumericUpDown_LostFocus">
<TextBox.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding AnyNumericErrors, RelativeSource={RelativeSource AncestorType={x:Type local:NumericUpDown}, AncestorLevel=1}}" Value="false">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
<DataTrigger Binding="{Binding AnyNumericErrors, RelativeSource={RelativeSource AncestorType={x:Type local:NumericUpDown}, AncestorLevel=1}}" Value="true">
<Setter Property="Background" Value="Red" />
<Setter Property="ToolTip" Value="There is an error" />
</DataTrigger>
</Style.Triggers>
<EventSetter Event="LostFocus" Handler="PART_NumericUpDown_LostFocus" />
</Style>
</TextBox.Resources>
<TextBox.Template>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost"
Grid.Column="0" />
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<RepeatButton x:Name="PART_IncreaseButton"
Click="PART_IncreaseButton_Click"
Content="UP" />
<RepeatButton x:Name="PART_DecreaseButton"
Grid.Row="1"
Click="PART_DecreaseButton_Click"
Content="Down"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</StackPanel>
</Grid>
C# Code Behind
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for NumericUpDown.xaml
/// </summary>
public partial class NumericUpDown : UserControl
{
public NumericUpDown()
{
InitializeComponent();
//AnyNumericErrors = true;
}
private String _NumericUpDownText;
public String NumericUpDownText
{
get { return _NumericUpDownText; }
set
{
_NumericUpDownText = value;
NotifyPropertyChanged("NumericUpDownText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void PART_NumericUpDown_LostFocus(object sender, RoutedEventArgs e)
{
CheckForErrors();
}
public void CheckForErrors()
{
try
{
int value = Int32.Parse(NumericUpDownText);
AnyNumericErrors = false;
}
catch (FormatException)
{
Debug.WriteLine("error");
AnyNumericErrors = true;
}
}
private Boolean m_AnyNumericErrors;
public Boolean AnyNumericErrors
{
get
{
return m_AnyNumericErrors;
}
set
{
m_AnyNumericErrors = value;
NotifyPropertyChanged("AnyNumericErrors");
}
}
#region DP
public Int32 LowerBound
{
get;
set;
}
public static readonly DependencyProperty LowerBoundProperty = DependencyProperty.Register("LowerBound", typeof(Int32), typeof(NumericUpDown));
#endregion
private void PART_IncreaseButton_Click(object sender, RoutedEventArgs e)
{
try
{
Int32 value = Int32.Parse(NumericUpDownText);
value++;
NumericUpDownText = value.ToString();
}
catch (Exception)
{
AnyNumericErrors = true;
}
}
private void PART_DecreaseButton_Click(object sender, RoutedEventArgs e)
{
try
{
Int32 value = Int32.Parse(NumericUpDownText);
value--;
NumericUpDownText = value.ToString();
}
catch (Exception)
{
AnyNumericErrors = true;
}
}
}
}
EDIT
The primary issue is that the DataTriggers are not working... at inception, the textbox is blue, but never changes. And when I press on up/down to increment/decrement the values, the events are getting called, but the value isn't changing in the UI
You should use DependencyProperties like:
public bool AnyNumericErrors
{
get { return (bool)GetValue(AnyNumericErrorsProperty); }
set { SetValue(AnyNumericErrorsProperty, value); }
}
// Using a DependencyProperty as the backing store for AnyNumericErrors. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnyNumericErrorsProperty =
DependencyProperty.Register("AnyNumericErrors", typeof(bool), typeof(NumericUpDown), new UIPropertyMetadata(false));
If that isn't enough, then remove the level restriction on you ancestor search.
I have a problem, and I have not found the solution yet. I woud like to create a base custom control and use it in another custom control. The base control works fine when I use it in a window, but when I use it in the other custom control, the binding does not work.
What's wrong with my code?
Code:
Model:
public class ElementModel
{
public string Name { get; set; }
public string FullName { get; set; }
}
The base control:
public class ListControl : Control
{
static ListControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ListControl), new FrameworkPropertyMetadata(typeof(ListControl)));
}
public ListControl()
{
SetValue(ElementListProperty, new List<ElementModel>());
}
public static readonly DependencyProperty ElementListProperty =
DependencyProperty.Register(
"ElementList",
typeof(List<ElementModel>),
typeof(ListControl),
new FrameworkPropertyMetadata(new List<ElementModel>())
);
public List<ElementModel> ElementList
{
get { return (List<ElementModel>)GetValue(ElementListProperty); }
set { SetValue(ElementListProperty, value); }
}
}
The Wrapper Control:
public class ListWrapper : Control
{
static ListWrapper()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ListWrapper), new FrameworkPropertyMetadata(typeof(ListWrapper)));
}
public ListWrapper()
{
SetValue(EMListProperty, new List<ElementModel>());
}
public static readonly DependencyProperty EMListProperty =
DependencyProperty.Register(
"EMList",
typeof(List<ElementModel>),
typeof(ListWrapper),
new FrameworkPropertyMetadata(new List<ElementModel>())
);
public List<ElementModel> EMList
{
get { return (List<ElementModel>)GetValue(EMListProperty); }
set { SetValue(EMListProperty, value); }
}
}
File 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:UIControl">
<Style TargetType="{x:Type local:ListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ListControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ListBox ItemsSource="{TemplateBinding ElementList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="Name:"/>
<TextBlock Text="{Binding Path=Name}" />
<Label Content="Full name:"/>
<TextBlock Text="{Binding Path=FullName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ListWrapper}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ListWrapper}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<local:ListControl ElementList="{TemplateBinding EMList}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If I put the controls in the window and binding properties, than the ListControl works fine and shows the elements, but the WrapperList does not.
<Window x:Class="MainApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:UIControl;assembly=UIControl"
Title="Window1" Height="304" Width="628">
<Grid>
<ui:ListControl x:Name="listCtr" ElementList="{Binding Path=EList}" HorizontalAlignment="Left" Width="300" />
<ui:ListWrapper x:Name="listWrp" EMList="{Binding Path=EList}" HorizontalAlignment="Right" Width="300" Background="Gray"/>
</Grid>
And the test data injection:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
List<ElementModel> elist = null;
public List<ElementModel> EList
{
get
{
if (elist == null)
{
elist = new List<ElementModel>();
ElementModel em = new ElementModel() { Name = "Apple", FullName = "Red Apple" };
elist.Add(em);
em = new ElementModel() { Name = "Pineapple", FullName = "Yellow Pineapple" };
elist.Add(em);
}
return elist;
}
}
}
Project archive
I don’t know why, but if I should wrap this list of element (List) into new collection type (example ElementCollection : ObservalbeCollection) and use this collection type in the dependency property, it works fine...
public static readonly DependencyProperty EMListProperty =
DependencyProperty.Register(
"EMList",
typeof(ElementCollection),
typeof(ListWrapper),
new FrameworkPropertyMetadata(new ElementCollection())
);
public ElementCollection EMList
{
get { return (ElementCollection)GetValue(EMListProperty); }
set { SetValue(EMListProperty, value); }
}
I couldn't find anywhere where you are setting the binding context:
http://msdn.microsoft.com/en-us/library/ms752347.aspx