I am trying to create a dependency property to pass a List<Object> to my custom validation class but I get the following error:
A 'Binding' cannot be set on the 'TimeSheetRowList' property of type 'WpfApp1_Services_Wrapper_2_252121760'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Here is my XAML code:
<UserControl x:Class="WpfApp1.Views.Installer"
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:WpfApp1.Views"
xmlns:validation="clr-namespace:WpfApp1.Services"
mc:Ignorable="d"
>
<Grid>
<DataGrid >
<DataGrid.Columns>
<DataGridTextColumn Header="Time from" >
<DataGridTextColumn.Binding>
<Binding Path="TimeFrom" StringFormat="HH:mm" ValidatesOnNotifyDataErrors="True">
<Binding.ValidationRules>
<validation:TimeIntervalValidation>
<validation:TimeIntervalValidation.Wrapper >
<validation:Wrapper TimeSheetRowList="{Binding DataList}"/>
</validation:TimeIntervalValidation.Wrapper>
</validation:TimeIntervalValidation>
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
public class TimeIntervalValidation:ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// method implementation
}
public Wrapper Wrapper { get; set; }
}
public class Wrapper : DependencyObject
{
public static readonly DependencyProperty TimeSheetRowCollectionProperty =
DependencyProperty.Register("TimeSheetRowList", typeof(IList<TimeSheetRow>),
typeof(Wrapper),
new FrameworkPropertyMetadata(
new ObservableCollection<TimeSheetRow>()
));
public ObservableCollection<TimeSheetRow> TimeSheetRowList
{
get { return (ObservableCollection<TimeSheetRow>)GetValue(TimeSheetRowCollectionProperty); }
set { SetValue(TimeSheetRowCollectionProperty, value); }
}
}
Tried to close project then clean and rebuild solution but doesn't work. To mention I am only getting the error mentioned above only when entering in XAML, otherwise if I just build solution I do not get any errors before going to XAML.
When you use typeof(IList<TimeSheetRow>) as property type argument of the Register method, you must also use that type for the property wrapper.
Besides that, you must not set any other value than null for the default value of collection-type properties. Otherwise all instances of the Wrapper class would use the same single default collection object.
Also adhere to naming conventions for the depenency property identifier field.
public static readonly DependencyProperty TimeSheetRowListProperty =
DependencyProperty.Register(
nameof(TimeSheetRowList),
typeof(IList<TimeSheetRow>),
typeof(Wrapper),
new PropertyMetadata(null));
public IList<TimeSheetRow> TimeSheetRowList
{
get { return (IList<TimeSheetRow>)GetValue(TimeSheetRowListProperty); }
set { SetValue(TimeSheetRowListProperty, value); }
}
Related
Hello,
After reading lots of topics about visibility binding for hours, I'm asking here because I don't manage to make my case works.
I have a grid with a custom attached property (type System.Windows.Visibily) which I want to use to display (or not) a textblock inside the grid (by binding). Also I want to change the visibility everytime the custom attached property change.
What I have done so far :
CustomProperties class :
public static class CustomProperties
{
public static readonly DependencyProperty starVisibilityProperty =
DependencyProperty.RegisterAttached("starVisibility",
typeof(System.Windows.Visibility), typeof(CustomProperties),
new FrameworkPropertyMetadata(null));
public static System.Windows.Visibility GetStarVisibility(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return (System.Windows.Visibility)element.GetValue(starVisibilityProperty);
}
public static void SetStarVisibility(UIElement element, System.Windows.Visibility value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(starVisibilityProperty, value);
}
}
Then here is my xaml :
<Grid Name="server1State" Grid.Row="1" local:CustomProperties.StarVisibility="Hidden">
<TextBlock Name="server1Star" Text="" FontFamily="{StaticResource fa-solid}" FontSize="30" Margin="10" Foreground="#375D81" Visibility="{Binding ElementName=server1State, Path=server1State.(local:CustomProperties.starVisibility)}"/>
</Grid>
But when I run my app, the textblock is absolutely not hidden, this is visible, and never change. I have tried lots of things with Path and also INotifyPropertyChanged but as I am working with static custom attached property, I didn't manage to make it works.
Maybe some of you could help me, thanks.
Your Binding.Path on the TextBlock is wrong.
Since I've read from your comment, that you prefer to use a boolean property, I'll show how to convert the bool value to a Visibility enumeration value using the library's BooleanToVisibilityConverter.
I think you may already got it, but then got confused due to your wrong Binding.Path:
CustomProperties.cs
public class CustomProperties : DependencyObject
{
#region IsStarVisibile attached property
public static readonly DependencyProperty IsStarVisibileProperty = DependencyProperty.RegisterAttached(
"IsStarVisibile",
typeof(bool),
typeof(CustomProperties),
new PropertyMetadata(default(bool)));
public static void SetIsStarVisibile(DependencyObject attachingElement, bool value) => attachingElement.SetValue(CustomProperties.IsStarVisibileProperty, value);
public static bool GetIsStarVisibile(DependencyObject attachingElement) => (bool)attachingElement.GetValue(CustomProperties.IsStarVisibileProperty);
#endregion
}
MainWindow.xaml
<Window>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid Name="Server1StateGrid"
CustomProperties.IsStarVisibile="False">
<TextBlock Text=""
Visibility="{Binding ElementName=Server1StateGrid,
Path=(CustomProperties.IsStarVisibile),
Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Window>
I have created Attached Property, for learning purposes but can't get successful result.
public class AQDatagridDependencyProperties: DependencyObject
{
public static void SetCustomDataSource(AQDataGrid element, string value)
{
element.SetValue(CustomDataSourceProperty, value);
}
public static string GetCustomDataSource(AQDataGrid element)
{
return (string)element.GetValue(CustomDataSourceProperty);
}
// Using a DependencyProperty as the backing store for DataSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CustomDataSourceProperty =
DependencyProperty.Register("CustomDataSource", typeof(string), typeof(AQDataGrid), new PropertyMetadata("obuolys"));
}
I've placed this attached property in my custom datagrid User Control which is implemented in UserView Page.
<Page x:Class="PDB.UsersView"
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:PDB"
xmlns:PDB ="clr-namespace:PDBapi;assembly=PDBapi"
xmlns:Wpf ="clr-namespace:AQWpf;assembly=AQWpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="Users"
Name="Users"
VisualBitmapScalingMode="LowQuality"
>
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
<Grid VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">
<Wpf:AQDataGrid DataContext="{Binding AQDatagridViewModel}" Wpf:AQDatagridDependencyProperties.CustomDataSource="Something" />
</Grid>
Question is how to bind that attached property value inside Custom Datagrid User Control? Example:
<UserControl x:Class="AQWpf.AQDataGrid"
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:AQWpf"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
Name="AQCustomDataGrid"
>
<!--Custom Data grid Implementation-->
<DataGrid x:Name="InstructionsDataGrid"
Grid.Row="1"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AQDataGrid}, Path=DataContext}"
Style="{StaticResource OptimizedAGDatagrid}"
ItemsSource="{Binding Data}"
CurrentItem="{Binding SelectedObject, Mode=TwoWay}"
CurrentColumn="{Binding CurrentColumn, Mode=TwoWay}"
CurrentCell="{Binding CurrentCells, Mode=TwoWay}"
Tag="<----How to bind here? ---->}"
>
Your attached property declaration is incorrect. You must call RegisterAttached instead of Register, and the third argument passed to the method must by the type of the class that declares the property.
Besides that, the declaring class does not need to be derived from DependencyObject, and could even be declared static:
public static class AQDatagridDependencyProperties
{
public static readonly DependencyProperty CustomDataSourceProperty =
DependencyProperty.RegisterAttached( // here
"CustomDataSource",
typeof(string),
typeof(AQDatagridDependencyProperties), // and here
new PropertyMetadata("obuolys"));
public static string GetCustomDataSource(AQDataGrid element)
{
return (string)element.GetValue(CustomDataSourceProperty);
}
public static void SetCustomDataSource(AQDataGrid element, string value)
{
element.SetValue(CustomDataSourceProperty, value);
}
}
You would set that property like
<local:AQDataGrid local:AQDatagridDependencyProperties.CustomDataSource="something" >
and bind to it by an expression like
Tag="{Binding Path=(local:AQDatagridDependencyProperties.CustomDataSource),
RelativeSource={RelativeSource AncestorType=UserControl}}"
As a note, you would typically declare the property as a regular dependency property in the AQDataGrid class.
You were defining a simple DepencyProperty. You have to use the DependencyProperty.RegisterAttached method to register the DependencyProperty as an attached property.
Also the owner type must be set to the declaring class' type (typeof(AQDatagridDependencyProperties)) and not the attaching type (typeof(AQDataGrid)):
AQDatagridDependencyProperties.cs
public class AQDatagridDependencyProperties : DependencyObject
{
public static readonly DependencyProperty CustomDataSourceProperty = DependencyProperty.RegisterAttached(
"CustomDataSource",
typeof(string),
typeof(AQDatagridDependencyProperties),
new FrameworkPropertyMetadata("obuolys", AQDatagridDependencyProperties.DebugPropertyChanged));
private static void DebugPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var oldValue = e.OldValue; // Set breakpoints here
var newValue = e.NewValue; // in order to track property changes
}
public static void SetCustomDataSource(DependencyObject attachingElement, string value)
{
attachingElement.SetValue(CustomDataSourceProperty, value);
}
public static string GetCustomDataSource(DependencyObject attachingElement)
{
return (string) attachingElement.GetValue(CustomDataSourceProperty);
}
}
Usage
<AQDataGrid AQDatagridDependencyProperties.CustomDataSource="Something" />
Inside the AQDataGrid control:
<DataGrid x:Name="InstructionsDataGrid"
Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=AQDataGrid}, Path=(AQDatagridDependencyProperties.CustomDataSource)}" />
I'm attempting to databind the ListBox SelectedItems attribute using an attached property I've created. I set up a class called ListBoxFix which is located in a folder called ControlFixes. It's code is a very simple dependency property shown below:
using System.Windows;
using System.Windows.Controls;
namespace QMAC.ControlFixes
{
public static class ListBoxFix
{
public static bool GetSelectedItemsBinding(ListBox element)
{
return (bool)element.GetValue(SelectedItemsBindingProperty);
}
public static void SetSelectedItemsBinding(ListBox element, bool value)
{
element.SetValue(SelectedItemsBindingProperty, value);
if (value)
{
element.SelectionChanged += (sender, args) =>
{
var x = element.SelectedItems;
};
}
}
public static readonly DependencyProperty SelectedItemsBindingProperty =
DependencyProperty.RegisterAttached("FixSelectedItemsBinding",
typeof(bool), typeof(FrameworkElement), new PropertyMetadata(false));
}
}
In my XAML code I have the following markup:
<Window x:Class="QMAC.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:fix="clr-namespace:QMAC.ControlFixes"
x:Name="Window"
DataContext="{Binding Main, Mode=OneWay, Source={StaticResource Locator}}"
Title="QMAC" Width="554.779" ResizeMode="CanMinimize" Height="539" Icon="logo.ico" >
<Grid Background="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" RenderTransformOrigin="0.593,0.948" Margin="0,0,0,1">
<ListBox x:Name="schoolListBox" HorizontalAlignment="Left" Margin="25,86,0,0" Width="274" FontSize="16" SelectionMode="Extended" ItemsSource="{Binding LocationList}" fix:ListBox.SelectedItemsBindingProperty="true" VerticalAlignment="Top" Height="364"></ListBox>
</Grid>
</Window>
Unfortunately, I'm getting the 3 errors to how I've setup my markup. They are
Error 1 The name "ListBox" does not exist in the namespace "clr-namespace:QMAC.ControlFixes".
Error 2 The attachable property 'SelectedItemsBindingProperty' was not found in type 'ListBox'.
Error 3 The property 'ListBox.SelectedItemsBindingProperty' does not exist in XML namespace 'clr-namespace:QMAC.ControlFixes'.
I'm mainly trying to understand why it's looking for ListBox in my ControlFixes namespace?
You declare and use the attached property in a wrong way. I would suggest you to read carefully this well written overview.
There are following mistakes in your code:
The owner type for your attached property is incorrectly specified as FrameworkElement.
The registered property name does not match the static field containing it
You try to use your attached property via the ListBox class although you have defined it in your ListBoxFix class.
The proper attached property definition should look similar to this:
public static class ListBoxFix
{
public static bool GetSelectedItemsBinding(ListBox element)
{
return (bool)element.GetValue(SelectedItemsBindingProperty);
}
public static void SetSelectedItemsBinding(ListBox element, bool value)
{
element.SetValue(SelectedItemsBindingProperty, value);
}
public static readonly DependencyProperty SelectedItemsBindingProperty =
DependencyProperty.RegisterAttached("SelectedItemsBinding",
typeof(bool), typeof(ListBoxFix), new PropertyMetadata(false));
}
Note that the ownerType parameter of the RegisterAttached() method provides the type of your class containing the attached property. Take a look on the name parameter too.
The proper usage of your attached property:
<ListBox fix:ListBoxFix.SelectedItemsBinding="true"/>
Update:
You might want to use your attached property in a "WPF" style. Then it would be better to design your class to be derived from DependencyObject. This is what MSDN states:
If your class is defining the attached property strictly for use on other types, then the class does not have to derive from DependencyObject. But you do need to derive from DependencyObject if you follow the overall WPF model of having your attached property also be a dependency property.
So I setup a simple Textbox
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding}" xmlns:Local="clr-namespace:MyProject">
<TextBox Name="txb_userActivity" IsEnabled="False" IsReadOnly="True">
<TextBox.Text>
<Binding Path="lastUserActivity">
</Binding>
</TextBox.Text>
</TextBox>
</Window>
I am trying to setup a Property:
namespace MyProject{
public partial class MainWindow : Window{
private DateTime _lastUserActivity = DateTime.Now;
public DateTime lastUserActivity{
set {
_lastUserActivity = value;
}
get {
return _lastUserActivity;
}
}
}
}
So that the Textbox will update it's value when the property is changed:
lastUserActivity = DateTime.Now;
My code isn't working, what should I do?
Your View needs a notification that it has to be updated.
You have to use either a DependencyProperty, or implement INotifyPropertyChanged, then your setter should look something like
private DateTime _lastUserActivity = DateTime.Now;
public DateTime LastUserActivity {
set {
_lastUserActivity = value;
}
get {
return _lastUserActivity;
OnPropertyChanged("LastUserActivity")
}
}
Furthermore, you should use a ViewModel and do not use this Property in your codebehind of the Window. If you want your Binding to work you have to set a DataContext to this ViewModel. When you leave it in the codebehind you'd have to set your Window as DataContext.
edit:
for DependencyObjects you should use DependencyProperty like this:
// Dependency Property
public static readonly DependencyProperty LastUserActivityProperty =
DependencyProperty.Register( "LastUserActivity", typeof(DateTime),
typeof(MainWindow), new FrameworkPropertyMetadata(DateTime.Now));
// .NET Property wrapper
public DateTime LastUserActivity
{
get { return (DateTime)GetValue(LastUserActivityProperty); }
set { SetValue(LastUserActivityProperty, value); }
}
but again:
If you wish to use bindings, you should become familiar with MVVM principles and use a ViewModel instead of codebehind. Something like this: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
edit2:
your DataContext is wrong.
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}" xmlns:Local="clr-namespace:MyProject">
I'm trying to add parameters to my custom validation rule. For this I defined a dependency object like this:
public class SettingsValueValidationDependencyObject : DependencyObject
{
public Custom.ValueType ValueTypeForValidation
{
get { return (Custom.ValueType)this.GetValue(ValueTypeForValidationProperty); }
set { this.SetValue(ValueTypeForValidationProperty, value); }
}
public static readonly DependencyProperty ValueTypeForValidationProperty = DependencyProperty.Register("ValueTypeForValidation", typeof(Custom.ValueType), typeof(SettingsValueValidationDependencyObject), new UIPropertyMetadata(Custom.ValueType.Int32Value));
}
My validation rule class looks like this:
public class SettingsValueValidationRule : ValidationRule
{
public SettingsValueValidationDependencyObject SettingsValueValidationDependencyObject
{
get;
set;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// validation...
}
}
xaml code:
<DataGridTextColumn Header="Value" Width="150">
<DataGridTextColumn.Binding>
<Binding Path="Value">
<Binding.ValidationRules>
<validators:SettingsValueValidationRule>
<validators:SettingsValueValidationRule.SettingsValueValidationDependencyObject>
<validators:SettingsValueValidationDependencyObject ValueTypeForValidation="{Binding ValueType}"/>
</validators:SettingsValueValidationRule.SettingsValueValidationDependencyObject>
</validators:SettingsValueValidationRule>
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
The two properties Value and ValueType both belong to the same object and the DataGrid's ItemsSource is bound to a list of these object. When I edit the Value cell, the ValueTypeForValidation property is always the default value (I also have a column to display the ValueType and its definitely another value). I also tried to update the BindingExpression manually in the Validate method but it won't work. What am I doing wrong?
There is no Binding in ValidationRules.
ValidationRules are not part of LogicalTree and so there is no DataContext to serve as Source in your Binding.
There are however few tricks on the internet how to make a ValidationRule "bindable".
Take a look at this tut:
Binding on a Non-UIElement