DependencyProperty doesnt pass Value after ValidateValueCallback [duplicate] - c#

When I set collection value from xaml within the control as such it seems to set value correctly:
<local:InjectCustomArray>
<local:InjectCustomArray.MyProperty>
<x:Array Type="local:ICustomType">
<local:CustomType/>
<local:CustomType/>
<local:CustomType/>
</x:Array>
</local:InjectCustomArray.MyProperty>
<local:InjectCustomArray>
if value is being set from style it does not appear to be hitting dependency property callback if you set initial value of it in ctor:
<local:InjectCustomArray>
<local:InjectCustomArray.Style>
<Style TargetType="local:InjectCustomArray">
<Style.Triggers>
<DataTrigger Binding="{Binding NonExistingProp}" Value="{x:Null}">
<Setter Property="MyProperty">
<Setter.Value>
<x:Array Type="local:ICustomType">
<local:CustomType/>
<local:CustomType/>
<local:CustomType/>
<local:CustomTypeTwo/>
</x:Array>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</local:InjectCustomArray.Style>
</local:InjectCustomArray>
Code of control:
public partial class InjectCustomArray : UserControl
{
public InjectCustomArray()
{
InitializeComponent();
// If following line is being commented out then setter value in xaml is being set, otherwise not.
MyProperty = new ICustomType[0];
}
public ICustomType[] MyProperty
{
get { return (ICustomType[])GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(ICustomType[]), typeof(InjectCustomArray), new PropertyMetadata(Callback));
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This is being hit only once from a ctor.
// If ctor is commented out then this value is being set from setter correctly.
}
}
Collection items code:
public interface ICustomType
{
}
public class CustomType : ICustomType
{
}
public class CustomTypeTwo : ICustomType
{
}
Question: Is there some optimisation within DependancyProperty when values of it is being set within close time range? What is the cause of such behavior?

The cause is that a local property value has higher precedence than a value from a Style Setter.
Instead of setting a local value, you could use the SetCurrentValue method:
public InjectCustomArray()
{
InitializeComponent();
SetCurrentValue(MyPropertyProperty, new ICustomType[0]);
}
See the Dependency Property Value Precedence article on MSDN for more details.

Related

How to acces to dependencyProperty within custom Canvas

i created a custom Canvas that inhirat from Canvas, i declared a new Dependency Property "NewMouseOver" that i want to affect via Setter in Trigger.
public class CanvaNetwork : Canvas
{
public CanvaNetwork() { }
public bool NewMouseOver
{
get { return (bool)GetValue(NewMouseOverProperty); }
set { SetValue(NewMouseOverProperty, value); }
}
public static readonly DependencyProperty NewMouseOverProperty =
DependencyProperty.Register("NewMouseOver", typeof(bool),
typeof(CanvaNetwork), new PropertyMetadata(false));
}
and here is my XAML :
<DataTemplate DataType="{x:Type local:Node}">
<local:CanvaNetwork x:Name="ItemCanvas_Node"
NewMouseOver="{Binding MyMouseOver}"
Background="Transparent">
<Path x:Name="Path_NodeProcess"
Stroke="Green"
Fill="Gray"
Stretch="None"
Data="{Binding Path =Geometryform}"
Visibility="{Binding Path=Visibility}">
</Path>
<local:CanvaNetwork.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="NewMouseOver" Value="True" />
</Trigger>
</local:CanvaNetwork.Triggers>
</local:CanvaNetwork>
</DataTemplate>
hera is my Node Class :
Public Node :DependencyObject
{
public static readonly DependencyProperty MyMouseOverProperty =
DependencyProperty.Register("MyMouseOver", typeof(bool), typeof(NodeProcess), new PropertyMetadata(true,new PropertyChangedCallback(On_MyMouseOver)));
private static void On_MyMouseOver(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//..some code
}
public bool MyMouseOver
{
get { return (bool)GetValue(MyMouseOverProperty); }
set { SetValue(MyMouseOverProperty, value); }
}
}
what i want is :
1-i have DependencyProperty : NewMouseOver (has get and set not like IsMouseOver in the original Canvas Class).
2-acces to NewMouseOver via Trigger/Setter and change the state of NewMouseOver .
3-via XAML : set a binding betwin : NewMouseOver (in CanvaNetwork) & MyMouseOver (in Node Class)
4-after that i'll use On_MyMouseOver (in Node Class) and MyMouseOver to make some stuff.
I think that I can answer the question about how to update the DependencyProperty in your canvas object.
To test it, I would define an "on changed" method for the dependency property. You can put a breakpoint here to verify that the dependency property is set.
class CanvaNetwork : Canvas
{
public CanvaNetwork ( ) { }
public static readonly DependencyProperty NewMouseOverProperty
= DependencyProperty.Register ( "NewMouseOver",
typeof (bool),
typeof (CanvaNetwork),
new PropertyMetadata (false, OnNewMouseOverChanged)) ;
public bool NewMouseOver
{
get { return (bool)GetValue (NewMouseOverProperty); }
set { SetValue (NewMouseOverProperty, value); }
}
public static void OnNewMouseOverChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
}
}
In the DataTemplate, you have to define the triggers within a Style.
<DataTemplate DataType="{x:Type local:Node}">
<local:CanvaNetwork x:Name="ItemCanvas_Node"
Background="red" Height="100" Width="100">
<Path x:Name="Path_NodeProcess"
Stroke="Green"
Fill="Gray"
Stretch="None"
Data="{Binding Path =Geometryform}"
Visibility="{Binding Path=Visibility}">
</Path>
<local:CanvaNetwork.Style>
<Style>
<Setter Property="local:CanvaNetwork.NewMouseOver" Value="False" />
<Style.Triggers>
<Trigger Property="Canvas.IsMouseOver" Value="True">
<Setter Property="local:CanvaNetwork.NewMouseOver" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</local:CanvaNetwork.Style>
</local:CanvaNetwork>
</DataTemplate>
You can only update a property with a trigger, if the default property is set within the style.
For this to work I have removed your attritute NewMouseOver="{Binding MyMouseOver}". From your list, points 1 and 2 work, but removing this attribute means that point 3 does not work.
However, I think that you are probably taking the wrong approach anyway. Wouldn't it be better to hook up the MouseOver event to a command property in your Node class, as described here:
How to make MouseOver event in MVVM?

Binding complex property of model to DataGridTextColumn and using Style to set value displayed

Let's say i have the code below...i have a Field class that holds a state and the actual value to display. I have model that defines an instance of this MyField class named Field1. In by DataGrid i am binding to this Field1 and using a style to display the value as well as color the background if IsStale is true. The problem is neither the value, nor the background is being colored. It appears the problem is that when used as is, the datacontext for the Style is MyData object and not in fact the MyField object, even though i specify the binding as "Field1". The error being printed out is "BindingExpression path error: 'IsStale' property not found on 'object' ''MyDataModel'".
How can properly bind to a complex property in a Datagrid's cell such that i can use multiple attributes of the bound model?
class MyField : BaseModel
{
private bool _isStale;
public bool IsStale
{
get { return _isStale; }
set
{
if (_isStale == value) return;
_isStale = value;
NotifyPropertyChanged("IsStale");
}
}
private double _value;
public double Value
{
get { return _value; }
set
{
if (_value.Equals(value)) return;
_value = value;
NotifyPropertyChanged("Value");
}
}
}
class MyDataModel
{
MyField Field1 {get; set;}
public MyData()
{
Field1 = new Field1();
//when ever underlying property of MyField changes, we need to fire property changed because xaml binds to Field1
Field1.PropertyChanged += (o,e) =>
{
MyField field = o as MyField;
if (field!=null)
NotifyPropertyChanged("Field1");
};
}
}
<DataGridTextColumn Header="Weight" Binding="{Binding Field1}" ElementStyle="{StaticResource DGCellStyle}"/>
Style:
<Style x:Key="DGCellStyle" TargetType="TextBlock">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Text" Value="{Binding Value, Converter={StaticResource NumberFormatConverter}, ConverterParameter=0.00}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsStale}" Value="True">
<Setter Property="Background" Value="Pink"/>
</DataTrigger>
</Style.Triggers>
</Style>

Setting a treeview items text colour in realtime based on a ViewModel property

In my WPF application I am using the MVVM pattern. My view has a treeview which I bind an observableCollection of objects as defined below. What I want to do is to change the colour of a tree item name when the bound object sets it’s dirty property to true. I can get it to set the colour when I first populate the tree but then it doesn’t reflect the changes when the property changes between false and true.
public class HierarchicalItem
{
private readonly ObservableCollection<HierarchicalItem> _children = new ObservableCollection<HierarchicalItem>();
public ViewModelBase ViewModel { get; set; }
public string Name
{
get { return ViewModel.ViewModelName; }
}
public ICollection<HierarchicalItem> Children
{
get { return _children; }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (_isSelected)
EventSystem.Publish(new SelectedViewModelMessage { SelectedViewModel = ViewModel });
}
}
public bool IsDirty
{
get { return ViewModel.IsDirty; }
}
}
This is the treeview xaml:
<TreeView Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=Views}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchicalItem}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Here is the collection that gets bound to the tree:
private readonly ObservableCollection<HierarchicalItem> _views = new ObservableCollection<HierarchicalItem>();
public ObservableCollection<HierarchicalItem> Views
{
get { return _views; }
}
The ViewModels that are referenced in the HierarchicalItem collection all derive from a base class that exposes the “IsDirty” property. This is definantly changing state so I’m not sure if I’ve made a coding mistake or if what I want to achieve can’t be done this way. The classes all use the “INotifyPropertyChanged” interface. Here is the “IsDirty” property in from the ViewModel base class:
public class ViewModelBase : ValidatableModel
{
#region Properties
private bool _isDirty;
public bool IsDirty
{
get { return _isDirty; }
protected set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
.
.
.
Etc
It's because your HierarchicalItem (the one you are having issues with) does not use a full INPC approach for its IsDirty property. The viewmodel does, but that is not enough, as the DataTemplate will be using the IsDirty property of the HierarchicalItem, so that needs to be full INPC property too
Changed that to this and it should be ok.
private bool _isDirty;
public bool IsDirty
{
get { return _isDirty; }
protected set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
Though for your use case you will need to figure out some way to fire that. Or another thing you could try would be to change the binding in HierarchicalItem DataTemplate to this
<DataTrigger Binding="{Binding ViewModel.IsDirty}" Value="True">

How to create a custom property?

I need to create a custom property meaning rather using
<Style x:Key="ABC" TargetType="Rectangle">
<Setter Property="Fill" Value="Red"/>
</Style>
I like to have something like Rectangle and assign it an ID so later when it is dropped on Canvas I can retrieve its ID.
<Style x:Key="ABC" TargetType="Rectangle">
<Setter Property="Fill" Value="Red"/>
**<Setter Property="ID" Value="1234567890-ABC"/>**
</Style>
How can I define that custom property?
Regards,
Amit
Define a custom attached property in a separate class:
public class Prop : DependencyObject
{
public static readonly DependencyProperty IDProperty =
DependencyProperty.RegisterAttached("ID", typeof(string), typeof(Prop), new PropertyMetadata(null));
public static void SetID(UIElement element, string value)
{
element.SetValue(IDProperty, value);
}
public static string GetID(UIElement element)
{
return (string)element.GetValue(IDProperty);
}
}
Then you can use this:
<Setter Property="local:Prop.ID" Value="1234567890-ABC"/>
local must be defined in the root element of your XAML appromimately like this:
xmlns:local="clr-namespace:AttPropTest"
where AttPropTest is the namespace of the assembly.
In code, you can determine the ID with Prop.GetID(myRect).

How to trigger DataTemplateSelector when property changes?

I have ContentPresenter with DataTemplateSelector:
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var model = item as ItemControlViewModel;
if (model.CurrentStatus == PrerequisitesStatus.Required)
{
return RequiredTemplate;
}
if (model.CurrentStatus == PrerequisitesStatus.Completed)
{
return FinishedTemplate;
}
...
return InProgressTemplate;
}
When CurrentStatus is changed, OnPropertyChanged is called.
I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate. Any suggestions?
Threre are similar questions:
1
2, but I don't want to use any DataTriggers, because of too much states.
Tried to play with DataTriggers
<ContentPresenter
Grid.Column="1"
Height="16"
Width="16"
Margin="3">
<ContentPresenter.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
</DataTrigger>
</ContentPresenter.Triggers>
</ContentPresenter>
But got an error:
Triggers collection members must be of type EventTrigger :(
As you requested an example with datatriggers in the comments, here you are:
A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger
And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. Better use a ContentControl when you want to have dynamic content.
See What's the difference between ContentControl and ContentPresenter?
And finally here's a suggestion to your DataTrigger issue. I have put it inside a style for reusability ....
XAML :
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="requiredTemplate">
<TextBlock Text="requiredTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<DataTemplate x:Key="completedTemplate">
<TextBlock Text="CompletedTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
<Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
<Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
</DataTrigger>
<!-- your other Status' here -->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
</Grid>
</Window>
I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector.
As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value.
Just as an extra choice - if you want to stick to your templates, just use s binding with converter.
I came up with a behavior that would theoretically do this.
C#:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public UpdateTemplateBehavior() : base() { }
protected override void OnAttached()
{
base.OnAttached();
Update();
}
void Update()
{
if (Content != null)
{
BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
AssociatedObject.Content = null;
BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
}
}
}
XAML:
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
<i:Interaction.Behaviors>
<Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
Value="{Binding SomeValue}"/>
</i:Interaction.Behaviors>
</ContentPresenter>
The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.
An update is not made unless the content is not null (my project-specific requirement). Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null, an update wouldn't occur until the value changes at least once.
Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter. I use a helper class that I did not include here for brevity. Keep that in mind when testing...

Categories

Resources