wpf C# update dependency property from another dependency property - c#

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!

Related

How to dynamically bind static DependencyProperty defined in a class to a property in another xaml class?

I have a static DependencProperty IsControlVisibleProperty in MyControl.xaml.cs. And its value is changed inside that same class. And I want to listen to this property in another control Visibility property whenever IsControlVisibleProperty value is changed.
MyControl.xaml.cs :
public partial class MyControl : UserControl
{
public static DependencyProperty IsControlVisibleProperty = DependencyProperty.Register(nameof(IsControlVisible), typeof(bool), typeof(MyControl));
public bool IsControlVisible
{
get{ return (bool)GetValue(IsControlVisibleProperty); }
set { SetValue(IsControlVisibleProperty, value); }
}
// In a function I am updating the dependency property
private void UpdateProp(bool isVisible)
{
this.SetValue(UserControl1.IsControlVisible, isVisible);
}
Now I want to use IsControlVisibleProperty value in another xaml file
SampleControl.xaml:
<UserControl x:Class="SampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
Height="300" Width="300">
<UserControl.Resources>
<converter:BooleanToVisibilityConverter x:Key="boolToVisConverter"/>
</UserControl.Resources>
<Grid>
<local:MyControl/>
<TextBlock Name="ErrorMessage" Text="Failed to run" Visibility="{Binding ElementName="ErrorMessage" , Path=(local:MyControl.IsControlVisible), Converter={StaticResource boolToVisConverter}, Mode=TwoWay}"/>
</Grid>
So my TextBlock (ErrorMessage) is not binding the IsVisibleProperty from MyControl.xaml.cs. I want IsVisibleProperty to be always binded with TextBlock(whereever the property changes it should alse change its visibility ) not just one time on contruction . Unfortunately I am unable to achieve this . Is there any other way to do so ?
The declaration of a DependencyProperty is always static but your IsControlVisible property is not static. In fact, you can't declare a static dependency property because the GetValue and SetValue method are not static.
What you should is to define an attached dependency property in a static class:
public static class MyProperties
{
public static readonly DependencyProperty IsControlVisibleProperty = DependencyProperty.RegisterAttached(
"IsControlVisible",
typeof(bool),
typeof(MyProperties));
public static void SetIsControlVisible(UIElement element, Boolean value)
{
element.SetValue(IsControlVisibleProperty, value);
}
public static bool GetIsControlVisible(UIElement element)
{
return (bool)element.GetValue(IsControlVisibleProperty);
}
}
You can then set this property on any UIElement (or whatever the type is in your get and set accessors) using the accessors like this:
MyProperties.SetIsControlVisible(this, true); //this = the UserControl
You bind to the attached property of the parent UserControl like this:
<TextBlock Name="ErrorMessage" Text="Failed to run"
Visibility="{Binding Path=(local:MyProperties.IsControlVisible),
Converter={StaticResource boolToVisConverter},
RelativeSource={RelativeSource AncestorType=UserControl}}" />

WPF: DependencyProperty works on TextBlock but not on custom control

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));

How to create a Dependency Property for Binding

I'm working on a "simple" case. I like to create a new custom control which implements a DependencyProperty. In the next step I like to create a binding for updating the properties in both directions. I've builded a simple sample for this case, but the binding doesn't seem to work. I've found a way for updating the DPControl's property by using the FrameworkPropertyMetadata, but I don't know whether it's also a good idea to use the OnPropertyChanged event.
HERE is my sample project:
My control contains simply a Label
<UserControl x:Class="WPF_MVVM_ListBoxMultiSelection.DPControl"
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:WPF_MVVM_ListBoxMultiSelection"
mc:Ignorable="d" Height="84.062" Width="159.641">
<Grid Margin="0,0,229,268">
<Label Content="TEST" x:Name="label" Margin="0,0,-221,-102"/>
</Grid>
</UserControl>
and implement a custom dependency property. Currently, I have also implemented the PropertyChanged method for the FramePropertyMetadata and set in this method the label's content, but I like to get it work in both directions.
public partial class DPControl : UserControl
{
public DPControl()
{
InitializeComponent();
}
public string MyCustomLabelContent
{
get { return (string)GetValue(MyCustomLabelContentProperty);}
set
{
SetValue(MyCustomLabelContentProperty, value);
}
}
private static void OnMyCustomLabelContentPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DPControl control = (DPControl)source;
control.label.Content = e.NewValue;
}
public static readonly DependencyProperty MyCustomLabelContentProperty = DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
OnMyCustomLabelContentPropertyChanged
)
);
I use this control simply in a Window by:
<local:DPControl MyCustomLabelContent="{Binding MyLabelContent, Mode=TwoWay}" Margin="72,201,286,34"/>
MyLabelContent is a property in the ViewModel, which has implemented also the INotifyPropertyChanged interface.
public class ViewModel_MainWindow:NotifyPropertyChanged
{
private string _myLabelContent;
public string MyLabelContent
{
get { return _myLabelContent; }
set { _myLabelContent = value;
RaisePropertyChanged();
}
}...
So how can I get it work: Using the binding feature with my new control on custom properties.
In your UserControl:
<Label
Content="{Binding MyCustomLabelContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
x:Name="label" Margin="0,0,-221,-102"/>
And get rid of that property-changed callback. All you need is the Binding.
I like to get it work in both directions
To make the dependency property two-way by default:
public static readonly DependencyProperty MyCustomLabelContentProperty =
DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
I omitted the unnecessary property change handler.
It can't usefully be two-way now, because Label.Content can't generate its own value. If you want your UserControl to set the value in its codebehind, that's easy:
MyCustomLabelContent = "Some arbitrary value";
If you did the binding like I showed you, that will update the Label in the UserControl XAML as well as the viewmodel property bound to the UserControl's dependency property.
If you want the XAML to set it, you'll need to
Lastly, this:
Margin="0,0,-221,-102"
Is not a good way to do layout. WPF layout with Grid, StackPanel, etc. is much easier and more robust.

Property Change not showing in Visual Studio designer

Using VS2015 I'm adding some custom functionality to a TextBlock for a small app and, since I can't derive from TextBlock itself (it's sealed), I'm deriving from UserControl.
In my xaml file, I have
<TextBlock x:Name="innerText"/>
As the only element within the usercontrol.
In my code-behind, I have the following used for accessing the text:
public string Label
{
get { return innerText.Text; }
set {
if (value != innerText.Text)
{
innerText.Text = value;
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Label"));
}
}
}
This works great when I'm running my app. On other pages, I am able to add instances of the control and set the "Label" property correctly. Unfortunately, the value of the "Label" property doesn't carry through to the inner textbox within the designer itself.
How can I get the value to update in the designer? While not strictly necessary (as I said, at run-time it works fine), it would make layout in the designer much easier for me.
Update:
I also tried using a DependencyProperty, with the same issue. Run-time works great, design-time shows nothing.
public string Label
{
get { return GetValue(LabelProperty).ToString(); ; }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(AutoSizingText), new PropertyMetadata(string.Empty));
And then, in the xaml, I set the DataContext for the entire control:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And tried to bind the Text value:
<TextBlock Text="{Binding Label}" />
I would recommend using a dependency property instead of relying on setting the innerText element's Text property. A dependency property will behave just like any other property on a control, including updating in design mode.
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(MyClassName), new PropertyMetadata(string.Empty));
And your XAML will look like this:
<UserControl x:Name="usr" ...>
...
<TextBlock Text="{Binding Label, ElementName=usr}" ... />
...
</UserControl>
Pro tip: Type propdp, then Tab, Tab to quickly create a dependency property.
Here's an example usage:
<local:MyUserControl Label="Le toucan has arrived"/>
Note: You do not need to set the DataContext to Self when using a dependency property, this will generally screw things up as the UserControl should not set it's own DataContext, the parent control should.

Make dependency object properties bindable as a static resource?

How to make an array of dependency object properties bindable for later binding as a static resource?
The code I have now, it seems that my DependencyObject bypasses the dependency property system...
I have the following class:
public class ValueMarker : DependencyObject
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register("Brush", typeof(Brush), typeof(ValueMarker), new FrameworkPropertyMetadata(Brushes.Aqua));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(ValueMarker), new FrameworkPropertyMetadata(0d));
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(double), typeof(ValueMarker), new FrameworkPropertyMetadata(0d));
public Brush Brush
{
get { return (Brush)GetValue(BrushProperty); }
set { SetValue(BrushProperty, value); }
}
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
In the XAML, I create a resource array of these with some bindings like so:
<x:Array Type="my:ValueMarker" x:Key="plainMarks">
<my:ValueMarker Brush="Red" Offset="-5" Value="{Binding Path=...}" />
<my:ValueMarker Brush="Orange" Offset="-5" Value="{Binding Path=...}"/>
<my:ValueMarker Brush="Orange" Offset="-5" Value="{Binding Path=...}"/>
<my:ValueMarker Brush="Red" Offset="-5" Value="{Binding Path=...}" />
</x:Array>
While debugging the bindings, I've noticed that should I remove the setter for the DP, the XAML would display an error saying the property is missing. It was my understanding that XAML uses DP system to assign value thus enabling binding. In this case, if the XAML expect a 'normal' property, binding is impossible. Anyone can enlighten me on how can I make it work?
The reason you cannot bind your ValueMarkers here is because:
1.They are not in the VisualTree of your window/usercontrol.
2.They are not object of Type that can inherit DataContext even if they are not part of Visual Tree.
So in order to make your ValueMarkers bind to the properties in the View DataContext, first of all you will have to derive them from Freezable class like below:
public class ValueMarker : Freezable
{
//All your Dependency Properties comes here//
protected override Freezable CreateInstanceCore()
{
return new ValueMarker();
}
}
After doing this you can simply bind your object like below:
<my:ValueMarker x:Key="vm1" Brush="Orange" Offset="-5" Value="{Binding Path=Text1}"/>
Here Text1 is property in Windows/usercontrols DataContext
Then you can use this resource as:
<TextBox Text="{Binding Value, Source={StaticResource vm1}, StringFormat=F2}"/>
Similarly you can create resource for other ValueMarkers to use them in binding.
You will not be able to bind by creating the x:Array as simply x:Array not lies in visualtree and does not inherit DataContext hence its elements also have no access to it.
If you still want to use the collection whose element should support binding, then you will need to create your own collection class that should inherit Freezable and exposes DependancyProperty to capture the DataContext and set it on child elements also.

Categories