,When I update an item in observableCollection "MyCollection" I want my custom TextBlock ( to execute function and modify its text. I think I should call function OnMYDataChanged:
<ListBox ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource MyTemplate}" >
<DataTemplate x:Key="MyTemplate" >
<Grid >...
<local:MyTextBlock Path="{Binding MyText}" />
where
public class MyTextBlock : TextBlock
{
public string Path
{ get {return (string)GetValue(PathProperty);}
set { SetValue(PathProperty, value); }
}
public static readonly DependencyProperty PathProperty =
DependencyProperty.Register("Path", typeof(string), typeof(MyTextBlock), new PropertyMetadata(OnMyDataChanged));
static void OnMyDataChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
Text = DoSomethingWithText(); //Does not work
}
When I change one item, OnMyDataChanged gets called, but
I get error there:
An object reference is required for the non-static field, method, or property
To add logic to the execution of a DependencyProperty, you can define a DependencyPropertyDescriptor for each DependencyProperty and add an AddValueChanged call with the necessary logic to it in your custom class's constructor. If you have a custom Grid class called DefinableGrid with a Columns property, the result is then (using C# 6's null-conditional operator ?.):
public int Columns
{
get { return (int) GetValue(ColumnsDependencyProperty); }
set { SetValue(ColumnsDependencyProperty, value); }
}
public static readonly DependencyProperty ColumnsDependencyProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(DefinableGrid), new PropertyMetadata(0));
DependencyPropertyDescriptor ColumnsPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ColumnsDependencyProperty, typeof(DefinableGrid));
public GridEx()
{
ColumnsPropertyDescriptor?.AddValueChanged(this, delegate
{
ColumnDefinitions.Clear();
for (int i = 0; i < Columns; i++)
ColumnDefinitions.Add(new ColumnDefinition());
});
}
The text property is not acessible because the callback function is static.
You need to cast the obj parameter to 'MyTextBlock', and throug that pointer you can access your object's properties.
Your source object needs to implement INotifyPropertyChanged for this to work (the object with the "MyText" property).
There is a great example implementation on MSDN.
As an aside, your datatemplate can be contained within the ListBox instead of being a static resource (might be less confusing if this is the only spot you want to use that data template):
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You want to use ObservableCollection.CollectionChanged event in this case
Related
I defined a custom loading spinner UserControl in a WPF UserContol library.
It has one dependency property:
public string SpinnerSourcePath { get => _spinner.Source.ToString(); set => _spinner.Source = (ImageSource)new ImageSourceConverter().ConvertFromString(value); }
public static readonly DependencyProperty SpinnerSourcePathProperty =
DependencyProperty.Register(nameof(SpinnerSourcePath), typeof(string), typeof(Spinner));
where _spinner is the Image.
(I tried it directly with ImageSource class but no dice)
The xaml looks like this:
<Image x:Name="_spinner" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
and I use it by defining it like:
<c:Spinner SpinnerSourcePath="/Test;component/_Resources/loading.png"/>
(The project name is Test, the Spinner control resides in a different project), nothing is displayed.
However, if I add the Source property directly in the Spinner definition:
<Image x:Name="_spinner" Source="/Test;component/_Resources/loading.png" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
it shows correctly...
This leads me to believe that the dependency property is wrong, but how ?
E1:
After trying to do the same steps on a different control it stopped working again.
This time I have a DP:
public static readonly DependencyProperty ValidationFunctionProperty =
DependencyProperty.Register(nameof(ValidationFunction), typeof(Func<string, bool>), typeof(ValidatedTextBox), new PropertyMetadata(OnAssignValidation));
public Func<string, bool> ValidationFunction {
get => (Func<string, bool>)GetValue(ValidationFunctionProperty);
set => SetValue(ValidationFunctionProperty, value);
}
private static void OnAssignValidation(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Debugger.Break();
}
Control usage:
<c:ValidatedTextBox x:Name="valid"
Text="Test"
ValidationFunction="{Binding Validation, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource test}}"/>
The converter is just a Debugger.Break() and return original
And finally the RelativeSource control is my MainWindow
public MainWindow() {
InitializeComponent();
}
public Func<string,bool> Validation => (s) => true;
(There is a problem with the Text DP as well, but I think I can solve that one on my own)
E2
Ok Pro problem was the RelativePath pointing to UserControl but it was placed in a Window
Your dependency property declaration is wrong, because the get/set methods of the CLR property wrapper must call the GetValue and SetValue methods of the DependencyObject base class (and nothing else).
Besides that, the property should also use ImageSource as its type:
public static readonly DependencyProperty SpinnerSourceProperty =
DependencyProperty.Register(
nameof(SpinnerSource), typeof(ImageSource), typeof(Spinner));
public ImageSource SpinnerSource
{
get { return (ImageSource)GetValue(SpinnerSourceProperty); }
set { SetValue(SpinnerSourceProperty, value); }
}
The Image element in the UserControl's XAML would use the property like this:
<Image Source="{Binding SpinnerSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Edit: a sample project can be found here.
I am using a ListBox inside my main window, which I later bind to an ObservableCollection. I use both a TextBlock and a custom control which I bind to the same property of the collection. My problem is that the TextBlock gets properly updated, whereas the custom control doesn’t (it gets default constructed but its Text property is never updated by the binding).
<ListBox Name="MyCustomItemList">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding ItemText}"/>
<local:MyCustomBlock Text="{Binding ItemText}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I implemented MyCustomBlock as a child of System.Windows.Controls.Canvas with a Text dependency property:
public class MyCustomBlock : Canvas
{
public MyCustomBlock() => Text = "<default>";
public MyCustomBlock(string text) => Text = text;
private static void TextChangedCallback(DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
...
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text), typeof(string), typeof(MyCustomBlock),
new FrameworkPropertyMetadata("", TextChangedCallback));
}
Finally, this is the data I bind to the ListBox in the MainWindow constructor:
public class MyCustomItem
{
public MyCustomItem(string text) => ItemText = text;
public string ItemText { get; set; }
}
public MainWindow()
{
InitializeComponent();
var list = new ObservableCollection<MyCustomItem>();
list.Add(new MyCustomItem("Hello"));
list.Add(new MyCustomItem("World"));
MyCustomItemList.ItemsSource = list;
}
Did I forget something in my setup? How come TextBlock.Text is seemingly properly updated but not MyCustomBlock.Text?
Dependency properties can get their value from several sources and so WPF employs a precedence system to determine which value applies. "Local" values (provided using SetValue or SetBinding) will override anything provided by the creating template.
In your case, your setting a "local" value in the constructor (presumably intending it to behave as a default value). A better way to set a default value is by providing it in the PropertyMetadata.
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text), typeof(string), typeof(MyCustomBlock),
new FrameworkPropertyMetadata("<default>", TextChangedCallback));
I am kind of new to WPF, I have the following scenario. I want to display a value of a 'Pressure Regulator' in the WPF Label, however, the pressure regulator is not necesserily connected. If it is, I display its value, if it's not I'd like to display e.g., "N/A" string.
To model this scenario I created a class 'OutputValues' with two Dependency Properties - PressureRegulatorValue (double type) and HasPressureRegualator (bool type):
public class OutputValues : DependencyObject, INotifyPropertyChanged
{
// ... some stuff
public bool HasPressureRegulator
{
get { return (bool)GetValue(HasPressureRegulatorProperty); }
set { SetValue(HasPressureRegulatorProperty, value); }
}
// Using a DependencyProperty as the backing store for HasPressureRegulator. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasPressureRegulatorProperty =
DependencyProperty.Register("HasPressureRegulator", typeof(bool), typeof(OutputValues), new PropertyMetadata(false, new PropertyChangedCallback(OutputValues.OnHasPressureRegulatorChanged)));
private static void OnHasPressureRegulatorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// not sure whether to put any code here
}
public double PressureRegulatorValue
{
get { return (double)GetValue(PressureRegulatorValueProperty); }
set { SetValue(PressureRegulatorValueProperty, value); }
}
// Using a DependencyProperty as the backing store for PressureRegulatorValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PressureRegulatorValueProperty =
DependencyProperty.Register("PressureRegulatorValue", typeof(double), typeof(OutputValues), new PropertyMetadata(0.0));
// ... some more stuff
}
Now I want to bind the Label which is in my GUI (it is part of UserControl called OutputValuesViewer) to an instance of this class. When I bind Label to a data source, the data source should be a dependency property I guess, so in OutputValuesViewer UserControl I created a DP:
public OutputValues OutputValues
{
get { return (OutputValues)GetValue(OutputValuesProperty); }
set { SetValue(OutputValuesProperty, value); }
}
// Using a DependencyProperty as the backing store for OutputValues. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OutputValuesProperty =
DependencyProperty.Register("OutputValues", typeof(OutputValues), typeof(OutputValuesViewer));
I initialize this property in the constructor of OutputValuesViewer:
public OutputValuesViewer()
{
InitializeComponent();
this.OutputValues = new OutputValues();
}
Finally my XAML looks like this:
<UserControl x:Class="OperationDescriptionEditor.OutputValuesViewer"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
xmlns:conversion="clr-namespace:OperationDescriptionEditor">
<!-- Unimportant stuff is left out -->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="btvc" />
<conversion:OutputValuesToPressureRegulatorStringConverter x:Key="ovtprsc" />
</UserControl.Resources>
<!-- More stuff -->
<Label Content="{Binding Path=OutputValues, Mode=OneWay, Converter={StaticResource ovtprsc}, UpdateSourceTrigger=PropertyChanged}" Name="lblPressureRegulatorValue" Grid.Row="0" Grid.Column="1" FontSize="9" />
So I bind the label to the OutputValues DP and I implemented a converter which creates the resulting string based on if HasPressureRegulator is true or not and also based on PressureRegulatorValue. I have a sort of a two-level DP. OutputValues is a DP and since it returns an instance of OutputValues class, I can access its two other DPs so:
OutputValues.HasPressureRegulator
OutputValues.PressureRegulatorValue
Now I would expect that if OutputValues.HasPressureRegulator changes, Label would automatically refresh itself. This does not happen though. I tried adding PropertyValueChanged callback to both:
OutputValues.HasPressureRegulator - the callback fires
OutputValues - the callback does not fire
So I'm guessing that Label does not know that it should refresh itself, because OutputValues property (which Label is bound to) does not inform it about a change.
The question is how should HasPressureRegulator DP inform its "parent" OutputValues DP that its value has changed?
As you can see from the code, I tried implementing INotifyPropertyChanged on OutputValues class, but that did not work.
Much thanks!
Below is my ParamModel class which is inherited from DependencyObject
public class ParamsModel : DependencyObject
{
public object MyProperty
{
get { return (object)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(object), typeof(ParamsModel), new PropertyMetadata(null));
public ParamsModel()
{
}
}
I have referred this class in my XAML like below
<TextBox Text="{Binding DataContext.MyName,ElementName=pageRoot}" />
<ListBox ItemsSource="{Binding MyItems}"
Width="500"
Height="500">
<local:ParamsModel MyProperty="{Binding DataContext.MyName,ElementName=pageRoot}" />
</ListBox>
I have put breakpoint at MyProperty setter which is not hitting at runtime, but the same class Constructor is hitting. Could anyone please help me on this
It is because the binding mechanism don't call the CLR property of dependency property. It calls GetValue/SetValue directly.
I have been trying to bind to a DependencyObject view model in a Store App and it just wouldn't work unless I put the PropertyMetaData parameter to null instead of new PropertyMetadata(null).
I'm using template selector for data grid in WPF.
I've got this code:
<l:ProblemTemplateSelector x:Key="problemTemplateSelector">
<l:ProblemTemplateSelector.ArithmeticTemplate>
<DataTemplate>
<Grid Background="LightBlue">
<l:ArithmeticUserControl Problem="{Binding ElementName=this}" />
</Grid>
</DataTemplate>
</l:ProblemTemplateSelector.ArithmeticTemplate>
</l:ProblemTemplateSelector>
In the usercontrol, I need to set in Problem property all the element, but I don't know what I'm doing wrong. Thanks.
UPDATE 1:
I'm gonna be more specific if you need details, I've got a class Exercise where is a user control:
public class Exercise : UserControl
{
public static readonly DependencyProperty ProblemProperty = DependencyProperty.Register(
"Problem", typeof(Problem), typeof(Exercise), new PropertyMetadata(null, ));
public virtual Problem Problem
{
get
{
return (Problem)GetValue(ProblemProperty);
}
set
{
SetValue(ProblemProperty, value);
}
}
}
This I've got another class where is derived from Exercise.
public partial class ArithmeticUserControl : Exercise
{
public ArithmeticUserControl()
{
InitializeComponent();
}
public override Problem Problem
{
get
{
return base.Problem;
}
set
{
base.Problem = value;
Arithmetic p = (Arithmetic)value;
Number1 = p.Number1;
Number2 = p.Number2;
Operator = p.Operation;
}
}
}
You should just be able to declare the binding as "{Binding}" and have it bind the Problem property to the data item.
EDIT: After seeing your example, you need to change how you're handling the Problem property. The WPF binding system does not make use of CLR properties, so the CLR properties exist only as a convenience for user code. Because of this, the code in your property is never being executed when the value is set through binding.
Instead, you need to handle property value changes by overriding OnPropertyChanged. For example, in your inherited class:
protected override OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(e.Property == ProblemProperty)
{
Arithmetic p = (Arithmetic)Problem;
Number1 = p.Number1;
Number2 = p.Number2;
Operator = p.Operation;
}
}
Simply use a {Binding} to pass a Data Item in the control.
If this won't help, along with your control add a TextBlock and see what is binds through straight binding:
<Grid Background="LightBlue">
<l:ArithmeticUserControl Problem="{Binding ElementName=this}" />
<TextBlock Text="{Binding}" Background="Green" ></TextBlock>
</Grid>
So on green background you shoudl see what is came to the ListBoxItem