Binding ProgressBar in CustomControl seems not to work - c#

I got a customcontrol that contains a ProgressBar among other elements.
<ProgressBar Value="{TemplateBinding CurrentProgress}"
MinValue="{TemplateBinding MinValue}"
MaxValue="{TemplateBinding MaxValue}"/>
<Label Content="{TemplateBinding CurrentProgress}"/>
In my .cs File, i defined all of these properties like this:
#region MaxProgress
public int MaxProgress
{
get { return (int)GetValue(MaxProgressProperty); }
set { SetValue(MaxProgressProperty, value); }
}
public static readonly DependencyProperty MaxProgressProperty =
DependencyProperty.Register("MaxProgress", typeof(int), typeof(GameFlowControl), new FrameworkPropertyMetadata(1000, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region CurrentProgress
public int CurrentProgress
{
get { return (int)GetValue(CurrentProgressProperty); }
set { SetValue(CurrentProgressProperty, value); }
}
public static readonly DependencyProperty CurrentProgressProperty =
DependencyProperty.Register("CurrentProgress", typeof(int), typeof(GameFlowControl), new FrameworkPropertyMetadata(50, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region MinProgress
public int MinProgress
{
get { return (int)GetValue(MinProgressProperty); }
set { SetValue(MinProgressProperty, value); }
}
public static readonly DependencyProperty MinProgressProperty =
DependencyProperty.Register("MinProgress", typeof(int), typeof(GameFlowControl), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
Binding those values to the label as shown above works fine, but apparently those bindings do not work for my ProgressBar. What i have tried so far:
Changing orders of Value, MinValue and MaxValue.
Added a typo to the TemplateBinding (like CurrentProgressXYZ), which gives me a compile error (so the properties are recognized)
Added default values to the Properties (see 0, 50, 1000).
Removed the binding and setting values directly: Value = 50, MinValue = 0, MaxValue=100 which shows the ProgressBar to show up as half filled.
Added Breakpoints to the getters of those properties, they were not triggered (which confuses me a lot!)
Any hints what could cause this?

Answer based on your question
The binding method you should use in this situation is Value="{Binding CurrentProgress, RelativeSource={RelativeSource AncestorType={x:Type GameFlowControl}}}". This traverses the visual tree upwards finding the first GameFlowControl control, then binding to the path from this relative position.
An Alternative
As an alternative if you are not utilizing the DataContext in the UserControl for any other purpose you could use the shorter method of binding.
Firstly you would need to assign the DataContext to the derived UserControl reference using something like this:-
public GasFlowControl()
{
InitializeComponent();
DataContext = this; //Set the DataContext to point to the control itself
}
Then your binding can be simplified down to this:-
<ProgressBar Value="{Binding CurrentProgress}"
MinValue="{Binding MinValue}"
MaxValue="{Binding MaxValue}"/>
<Label Content="{Binding CurrentProgress}"/>
To answer your confusion
Added Breakpoints to the getters of those properties, they were not triggered (which confuses me a lot!)
The reason you did not get any breakpoints triggering for property Getters and Setters is that the WPF framework does not use them. It internally calls GetValue(CurrentProgressProperty); and SetValue(CurrentProgressProperty, value); directly. They are there only for your convenience to include in your code and have the convenience of type casting and thus type checking when compiling.
If your code does not use them then they will never be called at all.

Related

How to declare a DependencyProperty that could also be set without binding

I know that I can declare a new DependencyProperty as such:
public String PropertyPath
{
get { return (String)GetValue(PropertyPathProperty); }
set { SetValue(PropertyPathProperty, value); }
}
public static readonly DependencyProperty PropertyPathProperty =
DependencyProperty.Register(nameof(PropertyPath), typeof(String),
typeof(NotEmptyStringTextBox),
new FrameworkPropertyMetadata(PropertyPath_PropertyChanged));
protected static void PropertyPath_PropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var ctl = d as NotEmptyStringTextBox;
var binding = new Binding(ctl.PropertyPath)
{
ValidationRules = { new NotEmptyStringRule() },
// Optional. With this, the bound property will be updated and validation
// will be applied on every keystroke.
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
ctl.StringBox.SetBinding(TextBox.TextProperty, binding);
}
But then the UserControl can only recieve a string with the name of the property to bind, and bind to it.
What I would like is to be able to have the same kind of comportment as "classical" properties, which you can either bind to, or give a static value.
My usage would be a boolean that modifies the display state of a UserControl, either statically with a fixed value or dynamically with a binding, all depending on the use case.
Maybe the way I made my dependency Property in the first place is incorrect, but here is how I can use it:
<inputboxes:NotEmptyStringTextBox
Grid.Column="1"
PropertyPath="Name"/>
This will bind the "Name" property from the DataContext, but I can't use it with a raw string, as it will make a BindingExpression error: "property not found"
EDIT:
I now have tried the following:
public bool Test
{
get { return (bool)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(nameof(Test), typeof(bool),
typeof(DamageTemplateListEditableUserControl));
I declared this new property, but I still cannot bind anything to it, only raw values are accepted
You shouldn't create a new binding in the callback. In fact, you don't need any callback at all.
Rename the dependency property to something better like "Text" and just bind the Text property of StringBox to the current value of your dependency property like this:
<TextBox x:Name="StringBox"
Text="{Binding Text, RelativeSource={RelativeSource AncestorType=local:NotEmptyStringTextBox},
UpdateSourceTrigger=PropertyChanged}" />
You can then set or bind the dependency property as usual.
If you really want a "PropertyPath" property, it shouldn't be a dependency property that you can bind something to but rather a simple CLR property that you can set to a string that represents a name of a property to bind to.
This is for example how the DisplayMemberPath property of an ItemsControl is implemented.

How to Bind an ObservableCollection of XyDataSeries using an Attached Property

I am creating a Charting application using SciChart.
I have added a chart modifier class which allows editing of the chart data but only the data currently displayed. I need to extend this class so that the full ObservableCollection of each XyDataSeries can be accessed.
I have implemented an attached property which I can bind to in the MainWindow DataContext however whenever I run the application the collection is showing as null in the modifier class. Please can you advise. Thanks
public class MoveBlockModifier : ChartModifierBase
{
public static readonly DependencyProperty XyFGDataProperty = DependencyProperty.RegisterAttached("XyFGData", typeof(ObservableCollection<XyDataSeries<double,double>>), typeof(MoveBlockModifier), new FrameworkPropertyMetadata(new ObservableCollection<XyDataSeries<double,double>>()));
public ObservableCollection<XyDataSeries<double, double>> XyFGData
{
get { return (ObservableCollection < XyDataSeries<double, double>>)GetValue(XyFGDataProperty); }
set { SetValue(XyFGDataProperty, value); }
}
public MoveBlockModifier()
{
_ghostSeries = new FastLineRenderableSeries()
{
Stroke = Colors.Black,
DataSeries = editingSeries,
Name = "GhostSeries",
StrokeThickness = 1,
Opacity = 0.75,
};
}
}
Public Class MainWindow: Window, INotifyPropertyChanged
{
private ObservableCollection<XyDataSeries<double, double>> _xyFGData;
public ObservableCollection<XyDataSeries<double, double>> XYFGData
{
get { return _xyFGData; }
set { _xyFGData = value; OnPropertyChanged("XYFGData"); }
}
}
XAML of MainWindow
<s:SciChartSurface x:Name="Chart2">
<s:SciChartSurface.ChartModifier>
<local:MoveBlockModifier FixStart="{Binding FixStart}" FixEnd="{Binding FixEnd}"
IsEnabled="{Binding ChartTwoMoveBlockEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
XyFGData="{Binding XYFGData, Mode=TwoWay}" />
</s:ModifierGroup>
</s:SciChartSurface.ChartModifier>
</s:SciChartSurface>
The question above seems incomplete / has some errors. You mention an attached property, which you define as this
public static readonly DependencyProperty XyFGDataProperty = DependencyProperty.RegisterAttached("XyFGData", typeof(ObservableCollection<XyDataSeries<double,double>>), typeof(MoveBlockModifier), new FrameworkPropertyMetadata(new ObservableCollection<XyDataSeries<double,double>>()));
public ObservableCollection<XyDataSeries<double, double>> XyFGData
{
get { return (ObservableCollection < XyDataSeries<double, double>>)GetValue(XyFGDataProperty); }
set { SetValue(XyFGDataProperty, value); }
}
...
but this isn't the way to define attached properties in WPF. Follow the MSDN documentation for how to register an attached property.
Secondly, you define a default value of new ObservableCollectionXyDataSeries<double, double> in your FrameworkPropertyMetadata, but this is a bad idea, because you will share one instance of ObservableCollectionXyDataSeries<double, double> statically across all instances of MoveBlockModifier. Have a look at Where to initialize reference type dependency properties for a custom control?
Lastly its an attached property that you want to define but in XAML you are not using it like an attached property.
This part:
is incorrect. See how an attached property is attached in XAML here.
Finally you bind MoveBlockModifier.XyFGData to a property XYFGData in your main window but the DataContext of the MoveBlockModifier might not be MainWindow.
I suggest starting again and fixing these errors!

Custom DependencyProperty set from DataTemplate

I'm working with a custom control that has several user-defined dependency properties. I'm running into the same issue described in this question.
My control is setting the default value of a custom dependency property in its constructor. When I use the control in a DataTemplate, the value set in the constructor is always used, even if I try to set it in XAML.
The answer to the linked question explains that the value set in the C# code has a higher priority, and a better approach would be to specify the default value in the dependency property's metadata.
In my case, I can't specify a default because the dependency property doesn't have a single default value that applies in all cases. The default values depend on another property, so I must look them up when the control is created and not when the property is registered.
Here's some code to help illustrate my problem:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty MyProperty =
DependencyProperty.Register(
"MyProperty",
typeof(int),
typeof(MyControl),
new FrameworkPropertyMetadata(
int.MinValue,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback("OnMyPropertyChanged")));
public MyControl() : base()
{
InitializeComponent();
this.MyProperty = GetDefaultPropertyValue();
}
public int MyProperty
{
get { return (int)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
private int GetDefaultPropertyValue()
{
// look up the appropriate default based on some other criteria
return 42;
// (in reality, the default value for "MyProperty"
// depends on the value of a "Mode" custom DependencyProperty.
// this is just hard coded for testing)
}
}
The XAML usage looks something like this:
<!-- View displays 4 (desired) -->
<local:MyControl MyProperty="4" />
<!-- View displays default of 42 (desired) -->
<local:MyControl />
<!-- View displays default of 42 (wanted 4) -->
<DataTemplate x:Key="MyTemplate">
<local:MyControl MyProperty="4"/>
</DataTemplate>
To summarize:
The desired behavior is that the value from XAML is used first. If the value is not specified in the XAML, then I would like to fallback to the default value set in the control's constructor.
If I just include the control directly in a view, I get the expected behavior. If the control is used inside a DataTemplate, then I always get the default set in the constructor (even when the data template explicitly sets another value).
Is there any other way to specify the default value when the control is used in a template? The only option I can think of is to break the control up into several separate but similar controls, each of which uses a default value that is registered with the dependency property (which removes the need to have the default set based on the a Mode property).
Setting the default value in OnApplyTemplate while adding a small check should solve this:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Only set the default value if no value is set.
if (MyProperty == (int)MyPropertyProperty.DefaultMetadata.DefaultValue)
{
this.MyProperty = GetDefaultPropertyValue();
}
}
Please note that although this will work, it's not ideal since setting the property's value by code will essentially clear any data bindings for this property. For example, the following binding will no longer work once you call MyProperty = 42 in code:
<local:MyControl MyProperty="{Binding SomeProperty}" />
It should be possible to set the value while maintaining any bindings by using SetCurrentValue(MyPropertyProperty, GetDefaultPropertyValue()); to modify the property instead of MyProperty = GetDefaultPropertyValue(), but I'm not sure I like that too much either.
A better solution
What I would do is introduce a new read-only property in addition to the existing one, which will act as a calculated property. For example:
private static readonly DependencyPropertyKey MyCalculatedPropertyPropertyKey =
DependencyProperty.RegisterReadOnly("MyCalculatedProperty", typeof(int), typeof(MyControl),
new PropertyMetadata(int.MinValue));
public static readonly DependencyProperty MyCalculatedPropertyProperty = MyCalculatedPropertyPropertyKey.DependencyProperty;
public int MyCalculatedProperty
{
get { return (int)GetValue(MyCalculatedPropertyProperty); }
private set { SetValue(MyCalculatedPropertyPropertyKey, value); }
}
private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl)d).MyCalculatedProperty = (int)e.NewValue;
}
public MyControl()
: base()
{
InitializeComponent();
MyCalculatedProperty = GetDefaultPropertyValue();
}

Dependency Property does not get or set Value

I have created a user control in WPF, and in the code behind I have created some dependency properties.
I added several WPF controls to my user control, one of the is a progress bar, so What I tried to do is to expose the Value progressBar property as below:
public static readonly DependencyProperty valueProperty = DependencyProperty.Register(
"Value",
typeof(Double),
typeof(MyUserControl),
new FrameworkPropertyMetadata(
ValuePropertyCallback));
private static void ValuePropertyCallback(DependencyObject controlInstance, DependencyPropertyChangedEventArgs args)
{
MyUserControl myUserControlInstance = (ProgressControl)controlInstance;
myUserControlInstance.progressBar.Value = (Double)args.NewValue;
}
public Double Value
{
get { return (Double)GetValue(valueProperty); }
set { SetValue(valueProperty, value); }
}
And in XAML I have written this:
<MyUserControl Name="myControl" Value="{Binding ProgressBarValue}" >
But It seems not to be working, neither setting nor getting the value.
I have a couple hours reviewing this but I cant realize what I am doing wrong.
Hope you can help me, Thank you in advance.
(Note: DataContext are defined previously and it is correct since this is the only binding that does not work)
valueProperty <----> "Value" does not match... (v/V) :=)
Have you tried Mode=TwoWay:
<MyUserControl Name="myControl" Value="{Binding ProgressBarValue, Mode=TwoWay}" >
I have also used PropertyMetadata instead of FrameworkPropertyMetadata
Try changing the name of your dependency property to be PascalCased:
ValueProperty
You might also want to look at BindsTwoWayByDefault to make sure changes to your DP are written to the source object.
Turns out that my problem was that I didnt implement the INotifyPropertyChanged interface, so, the changes I did were not showed. But since I am new at WPF using MVVM I didnt know that.
But you just need to create an ObservableObject, then your viewModel class has to inherit from it.
Here is an example to create the ObservableObject class and how to inherit from it.

C# WPF custom control not responding to XAML properties at design-time?

I've created a UserControl which is essentially a button. It's got an Image and a Label on it and I've created two properties to set the Image's source and the Label's text like so:
public ImageSource Icon
{
get { return (ImageSource)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); icon.Source = value; }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NavigationButton));
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); label.Content = value; }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(NavigationButton));
However, when I've added the control to my Page, the controls wont respond to any properties I set in XAML, e.g. <controls:MusicButton Icon="/SuCo;component/Resources/settings.png/> does nothing.
What am I doing wrong?
CLR properties that wrap dependency properties should never have any logic other than calling GetValue and SetValue. That is because they may not even be called. For example, the XAML compiler will optimize by calling GetValue/SetValue directly rather than using your CLR property.
If you need to execute some logic when a dependency property is changed, use metadata:
public ImageSource Icon
{
get { return (ImageSource)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NavigationButton), new FrameworkPropertyMetadata(OnIconChanged));
private static void OnIconChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
//do whatever you want here - the first parameter is your DependencyObject
}
EDIT
In my first answer, I assumed your control's XAML (be it from a template or directly in a UserControl) is correctly hooked up to the properties. You haven't showed us that XAML, so it was perhaps an incorrect assumption. I'd expect to see something like:
<StackPanel>
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
And - importantly - your DataContext must be set to the control itself. You can do this in various different ways, but here is a very simple example of setting it from the code behind:
public YourControl()
{
InitializeComponent();
//bindings without an explicit source will look at their DataContext, which is this control
DataContext = this;
}
Have you tried setting the text property as well? The source of an image may just be wrong. Text is much more straight forward.
Also, in your example, you missed a quotation mark. So if it's copied from your real code, you may want to check that.
Barring those minor admittedly unlikely causes for your problem, I'd suggest setting the properties in code to check whether that has any effect. If it has, then you should really check your XAML.
Since you haven't posted the rest of your code, I can't really tell if you have problems somewhere else that might affect the control.
And yes, I know I'm not very helpful, but I've been working with WPF for only a little while. Hope it helps anyway.

Categories

Resources