Dependency property not assigned when Loaded event fired - c#

MainWindow is using custom control:
<Window ... >
<wpfCustomControlLibrary1:CustomControl Parameter="{Binding ParameterValue}" />
</Window>
View-model of MainWindow contains ParameterValue for binding:
internal class MainViewModel
{
private string parameterValue;
public MainViewModel()
{
this.ParameterValue = "Test";
}
public string ParameterValue
{
get { return this.parameterValue; }
set
{
this.parameterValue = value;
$"Setter: {value != null}".TraceTime();
}
}
}
In custom control, I need to perform additional actions in Loaded event handler, and for this I need Parameter value:
public class CustomControl : Control
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
public CustomControl()
{
this.Loaded += this.OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
// HERE I need to use Parameter value
$"OnLoaded: {this.Parameter != null}".TraceTime();
}
public static readonly DependencyProperty ParameterProperty =
DependencyProperty.Register("Parameter", typeof(string), typeof(CustomControl), new PropertyMetadata(null, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
$"DP: {dependencyPropertyChangedEventArgs.NewValue != null}".TraceTime();
}
}
But at the moment when Loaded event triggers, Parameter value is still null. Here is trace output:
[14:24:13.961] Setter: True
[14:24:14.173] OnLoaded: False
[14:24:14.235] DP: True
You can see that despite actual ParameterValue has already been set, Parameter dependency property evaluate it with delay and after OnLoaded fires.
What custom control event can I use to be sure that all dependency properties evaluated?
I can use PropertyChangedCallback for initialization, but it will became more complicated if I would have several parameters.
By the way, when value assigning directly Parameter="Test", there is no delay, but I need binding.
public static void TraceTime(this object value)
{
System.Diagnostics.Trace.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] {value}");
}

Related

Trigger Dependency Property Changed when child Dependency Property has been changed

I have the following class:
public class dm_fourvalues : DependencyObject
{
[JsonProperty]
public double First
{
get { return (double)GetValue(FirstProperty); }
set
{
SetValue(FirstProperty, value);
}
}
public static readonly DependencyProperty FirstProperty =
DependencyProperty.Register("First", typeof(double), typeof(dm_fourvalues),
new FrameworkPropertyMetadata(1d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnFirstPropertyChanged)));
private static void OnFirstPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
dm_fourvalues uci = sender as dm_fourvalues;
if (uci != null)
{
uci.OnFirstChanged();
}
}
private void OnFirstChanged()
{
Console.WriteLine("first on dm fourvalues changed");
}
Now if use this class as identical property on another object, when the First property gets changed,
this object's OnChanged is not triggered, thus also a binding would not work.
What defines that a parent dependencyobject has been changed?

How to define and raise an event inside a static method (a PropertyChangedCallback)

I've defined an event in a custom control (Field) named ValueChanged.
public static event EventHandler<ValueChangedEventArgs> ValueChanged;
And a dependency property Value.
public string Value
{
get => (string)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(string), typeof(Field),
new PropertyMetadata(OnValuePropertyChanged));
I need to fire my event when this value changes (if FireValueChanged is true).
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool fire = (bool)d.GetValue(FireValueChangedProperty);
if (fire) ValueChanged?.Invoke(d, new ValueChangedEventArgs($"{e.NewValue}", $"{e.OldValue}"));
}
This is the ValueChangedEventArgs class
public class ValueChangedEventArgs : EventArgs
{
public string NewValue { get; }
public string OldValue { get; }
//Other calculated properties...
public ValueChangedEventArgs(string newValue, string oldValue)
{
NewValue = newValue;
OldValue = oldValue;
}
}
But in my main window it says that
cannot set the handler because the event is a static event.
And when I try to compile it says that
the property 'ValueChanged' does not exist in the XML namespace 'clr-namespace: ...'.
If I try to set the event as non static, I cannot use it inside static OnValuePropertyChanged method.
You can access the control that the value was changed for in your OnValuePropertyChanged method like this (I've named the control class MyControl):
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool fire = (bool)d.GetValue(FireValueChangedProperty);
var ctrl = (MyControl)d;
if (fire)
ctrl.ValueChanged?.Invoke(d, new ValueChangedEventArgs($"{e.NewValue}", $"{e.OldValue}"));
}
Then you can remove the static and change the event to be an event on instance level:
public event EventHandler<ValueChangedEventArgs> ValueChanged;

How to Bind to CaretIndex aka curser position of an Textbox

Hi I'm trying to bind to the TextBox.CaretIndex property which isn't a DependencyProperty, so I created a Behavior, but it doesn't work as expected.
Expectation (when focused)
default = 0
if I change the value in my view it should change the value in my viewmodel
if I change the value in my viewmodel it should change the value in my view
Current behavior
viewmodel value gets called ones when the window opens
Code-behind
public class TextBoxBehavior : DependencyObject
{
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register(
"CursorPosition",
typeof(int),
typeof(TextBoxBehavior),
new FrameworkPropertyMetadata(
default(int),
new PropertyChangedCallback(CursorPositionChanged)));
public static void SetCursorPosition(DependencyObject dependencyObject, int i)
{
// breakpoint get never called
dependencyObject.SetValue(CursorPositionProperty, i);
}
public static int GetCursorPosition(DependencyObject dependencyObject)
{
// breakpoint get never called
return (int)dependencyObject.GetValue(CursorPositionProperty);
}
private static void CursorPositionChanged(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
// breakpoint get never called
//var textBox = dependencyObject as TextBox;
//if (textBox == null) return;
}
}
XAML
<TextBox Text="{Binding TextTemplate,UpdateSourceTrigger=PropertyChanged}"
local:TextBoxBehavior.CursorPosition="{Binding CursorPosition}"/>
Further Information
I think there is something really wrong here because I need to derive it from DependencyObject which was never needed before, because CursorPositionProperty is already a DependencyProperty, so this should be enough. I also think I need to use some events in my Behavior to set my CursorPositionProperty correctly, but I don't know which.
After fighting with my Behavior i can present you a 99% working solution
Behavior
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfMVVMTextBoxCursorPosition
{
public class TextBoxCursorPositionBehavior : DependencyObject
{
public static void SetCursorPosition(DependencyObject dependencyObject, int i)
{
dependencyObject.SetValue(CursorPositionProperty, i);
}
public static int GetCursorPosition(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(CursorPositionProperty);
}
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register("CursorPosition"
, typeof(int)
, typeof(TextBoxCursorPositionBehavior)
, new FrameworkPropertyMetadata(default(int))
{
BindsTwoWayByDefault = true
,DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
}
);
public static readonly DependencyProperty TrackCaretIndexProperty =
DependencyProperty.RegisterAttached(
"TrackCaretIndex",
typeof(bool),
typeof(TextBoxCursorPositionBehavior),
new UIPropertyMetadata(false
, OnTrackCaretIndex));
public static void SetTrackCaretIndex(DependencyObject dependencyObject, bool i)
{
dependencyObject.SetValue(TrackCaretIndexProperty, i);
}
public static bool GetTrackCaretIndex(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(TrackCaretIndexProperty);
}
private static void OnTrackCaretIndex(DependencyObject dependency, DependencyPropertyChangedEventArgs e)
{
var textbox = dependency as TextBox;
if (textbox == null)
return;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (!oldValue && newValue) // If changed from false to true
{
textbox.SelectionChanged += OnSelectionChanged;
}
else if (oldValue && !newValue) // If changed from true to false
{
textbox.SelectionChanged -= OnSelectionChanged;
}
}
private static void OnSelectionChanged(object sender, RoutedEventArgs e)
{
var textbox = sender as TextBox;
if (textbox != null)
SetCursorPosition(textbox, textbox.CaretIndex); // dies line does nothing
}
}
}
XAML
<TextBox Height="50" VerticalAlignment="Top"
Name="TestTextBox"
Text="{Binding MyText}"
vm:TextBoxCursorPositionBehavior.TrackCaretIndex="True"
vm:TextBoxCursorPositionBehavior.CursorPosition="{Binding CursorPosition,Mode=TwoWay}"/>
<TextBlock Height="50" Text="{Binding CursorPosition}"/>
there is just on thing i don't know why it doesn't work => BindsTwoWayByDefault = true. it has no effect on the binding as far as i can tell you because of this i need to set the binding mode explicit in XAML
I encountered a similar problem, and the easiest solution for me was to inherit from TextBox and add a DependencyProperty. So it looks like this:
namespace UI.Controls
{
public class MyTextBox : TextBox
{
public static readonly DependencyProperty CaretPositionProperty =
DependencyProperty.Register("CaretPosition", typeof(int), typeof(MyTextBox),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnCaretPositionChanged));
public int CaretPosition
{
get { return (int)GetValue(CaretPositionProperty); }
set { SetValue(CaretPositionProperty, value); }
}
public MyTextBox()
{
SelectionChanged += (s, e) => CaretPosition = CaretIndex;
}
private static void OnCaretPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MyTextBox).CaretIndex = (int)e.NewValue;
}
}
}
... and in my XAML:
xmlns:controls="clr-namespace:IU.Controls"
...
<controls:MyTextBox CaretPosition="{Binding CaretPosition}"/>
... and CaretPosition property in the View Model of course. If you're not going to bind your View Model to other text-editing controls, this may be sufficient, if yes - you'll probably need another solution.
The solution from WiiMaxx has the following problems for me:
The caret index in the text box is not changed when the view model property is changed from the code.
This was also mentioned by Tejas Vaishnav in his comment to the solution.
BindsTwoWayByDefault = true does not work.
He stated that it is strange that he needs to inherit from DependencyObject.
The TrackCaretIndex property is only used for initialisation and it felt kind of unnecessary.
Here is my solution which solves those problems:
Behavior
public static class TextBoxAssist
{
// This strange default value is on purpose it makes the initialization problem very unlikely.
// If the default value matches the default value of the property in the ViewModel,
// the propertyChangedCallback of the FrameworkPropertyMetadata is initially not called
// and if the property in the ViewModel is not changed it will never be called.
private const int CaretIndexPropertyDefault = -485609317;
public static void SetCaretIndex(DependencyObject dependencyObject, int i)
{
dependencyObject.SetValue(CaretIndexProperty, i);
}
public static int GetCaretIndex(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(CaretIndexProperty);
}
public static readonly DependencyProperty CaretIndexProperty =
DependencyProperty.RegisterAttached(
"CaretIndex",
typeof(int),
typeof(TextBoxAssist),
new FrameworkPropertyMetadata(
CaretIndexPropertyDefault,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
CaretIndexChanged));
private static void CaretIndexChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
if (dependencyObject is not TextBox textBox || eventArgs.OldValue is not int oldValue || eventArgs.NewValue is not int newValue)
{
return;
}
if (oldValue == CaretIndexPropertyDefault && newValue != CaretIndexPropertyDefault)
{
textBox.SelectionChanged += SelectionChangedForCaretIndex;
}
else if (oldValue != CaretIndexPropertyDefault && newValue == CaretIndexPropertyDefault)
{
textBox.SelectionChanged -= SelectionChangedForCaretIndex;
}
if (newValue != textBox.CaretIndex)
{
textBox.CaretIndex = newValue;
}
}
private static void SelectionChangedForCaretIndex(object sender, RoutedEventArgs eventArgs)
{
if (sender is TextBox textBox)
{
SetCaretIndex(textBox, textBox.CaretIndex);
}
}
}
XAML
<TextBox Height="50" VerticalAlignment="Top"
Name="TestTextBox"
Text="{Binding MyText}"
viewModels:TextBoxAssist.CaretIndex="{Binding CaretIndex}"/>
Some clarifications for the differences:
View model property changes work now because the caret index on the TextBox is set at the end of CaretIndexChanged.
The BindsTwoWayByDefault was fixed by using the according FrameworkPropertyMetadata constructor parameter.
Inheriting from DependencyObject was only necessary because DependencyProperty.Register was used instead of DependencyProperty.RegisterAttached.
Without the TrackCaretIndex property I had the problem that the propertyChangedCallback for the FrameworkPropertyMetadata was never called to properly initialize things. The problem occurs only when the default value for the FrameworkPropertyMetadata match the value of the view model property right from the start and the view model property is not changed. That's why I used this random default value.
As you said, the TextBox.CaretIndex Property is not a DependencyProperty, so you cannot data bind to it. Even with your own DependencyProperty, it won't work... how would you expect to be notified when TextBox.CaretIndex Property changes?

DependencyProperty of Custom Type won't fire propertychanged callback

Premise: I read all the others threads about similar issues but none of those solved my problem.
I have a UserControl (SummarySource) with 3 DP:
public static DependencyProperty QueryProperty;
public static DependencyProperty MaxRowsPerPageProperty;
public static DependencyProperty OpcSessionProperty;
And the respective public getters and setters:
[Category("Common")]
public String Query
{
get { return (String)GetValue(QueryProperty); }
set { SetValue(QueryProperty, value); }
}
[Category("Common")]
public UInt32 MaxRowsPerPage
{
get { return (UInt32)GetValue(MaxRowsPerPageProperty); }
set { SetValue(MaxRowsPerPageProperty, value); }
}
[Category("Common")]
public UaSession OpcSession
{
get { return (UaSession)GetValue(OpcSessionProperty); }
set { SetValue(OpcSessionProperty, value); }
}
The problem is that tha PropertyChanged Callback for the "OpcSession" variable (the only one that is a custom type) isn't fired.
Static Constructor
OpcSessionProperty = DependencyProperty.Register("OpcSession", typeof(UaSession), typeof(SummarySource), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnSessionChanged)));
The Callback
private static void OnSessionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("He3e1");
SummarySource thisControl = (SummarySource)sender;
if (thisControl.DataContext != null)
{
((DataRetriever)thisControl.DataContext).SetOpcSession((UaSession)e.NewValue);
}
}
The MessageBox is never showed. If I put a MessageBox.Show on the other callbacks I can see the message when Load the form that use the control or change the value in xaml.
The .Xaml
<Window.DataContext>
<cs:UaSession x:Name="opcSession" EndpointUrl="opc.tcp://192.168.200.11:62543/Runtime"/>
</Window.DataContext>
<control:SummarySource x:Key="qq" MaxRowsPerPage="25" OpcSession="{Binding Path=DataContext, ElementName=window, PresentationTraceSources.TraceLevel=High, Mode=TwoWay}" />
No Binding Errors in output

Changing standard property into a DependencyProperty

In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?
I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}

Categories

Resources