I'm new to wpf, as the title suggests. I was using wpf as it was winforms (what's all that binding non sense anyway) until, of course, I tried it and got blown away.
So I was digging into user controls and dependency properties. I read that, in order to get the ui to remain in sync with what's under the hood you need to use observable collections, notifypropertychanged / changing and dependency properties for the stuff that you use.
My question is this:
Let's say I have this dep. prop. for a user control (type of Media.Color) :
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
The xaml uses it for a binding, it works, all is good. But, when it gets updated, I would like to do something with it in code.
So I tried putting a Console.writeline("fired") like so:
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set {
Console.WriteLine("Fired");
SetValue(ColorProperty, value);
}
}
No dice. Could someone please enlighten me how this stuff works? I am obviously missing something (just the other day someone on stack told me about MouseCapture so... ).
Thank you for your time.
Edit
http://www.wpftutorial.net/DependencyProperties.html
Basically it says, in big bold letters,
Important: Do not add any logic to these properties, because they are
only called when you set the property from code. If you set the
property from XAML the SetValue() method is called directly.
If you are using Visual Studio, you can type propdp and hit 2x tab to
create a dependency property.
And goes on to explain why and how you should proceed.
Solution
So, I tried what #Krishna suggested, and my user control crashed and burned.
Here was my dep prop. (as it was before asking this question).
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(ColorPickerMaster), new PropertyMetadata(default(Color)));
Turns out the problem was using
(...) new Prop.Metadata(null, OnPropChanged)
Using
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(ColorPickerMaster), new PropertyMetadata(OnColorChanged));
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine(e.NewValue);
}
yields a beautiful win.
Thank you for your time and answers.
When it comes to DependencyProperties you use property changed callback to track changes to your property like below example. And then you use e.NewValue and e.OldValue to write your logic. More about DependencyProperties on MSDN
public Color color
{
get { return (Color)GetValue(colorProperty); }
set { SetValue(colorProperty, value); }
}
public static readonly DependencyProperty colorProperty =
DependencyProperty.Register("color", typeof(Color), typeof(YourClass), new PropertyMetadata(null,colorChanged));
private static void colorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
YourClass c = d as YourClass;
if(c!=null)
{
}
}
From MSDN - XAML Loading and Dependency Properties:
The current WPF implementation of its XAML processor is inherently dependency property aware. The WPF XAML processor uses property system methods for dependency properties when loading binary XAML and processing attributes that are dependency properties. This effectively bypasses the property wrappers. When you implement custom dependency properties, you must account for this behavior and should avoid placing any other code in your property wrapper other than the property system methods GetValue and SetValue.
If you want to add custom logic in a setter you will have to make it a simple field (not a DependecyProperty) implement INotifyPropertyChanged and bind to it.
Related
I am trying to have images in my page update / re-draw when I change their ImageSource - this will help me have them re-load async.
I thought having a bindable property for the imageSource bound to the image was a start, but it is not updating the image. I have tried a large number of ways including a viewModel approach with an OnPropertyChanged event, but I don't think I quite understood this.
This binding also has to be done in code, this is just how the app is written (minimal xaml).
So far my general approach has been:
Bindable property
public static readonly BindableProperty ImageFileProperty =
BindableProperty.Create("ImageProperty", typeof(string),
typeof(CustomImageClass));
public string ImageProperty
{
get
{
return (string)GetValue(ImageFileProperty);
}
set
{
SetValue(ImageFileProperty, value);
}
}
Inside the CustomImageClass constructor:
this.SetBinding(ImageFileProperty, "ImageProperty");
From here I would like to to have the image change when I update the ImageSource and have the image change. I hope this is specific enough, I think all the different examples bound to the xaml have confused me in how I need to do it in code.
Sorry the poor english
I guess there's a lot of problem here...
If I understand well, you wanna create a BindableProperty in your CustomImageClass. Is that right?
If yes, so you can use the default convention to the bindable property name's, like this (note that I changed the type too):
public static readonly BindableProperty ImageFileProperty =
BindableProperty.Create("ImageFile", typeof(ImageSource),
typeof(CustomImageClass));
public ImageSource ImageFile
{
get{ return (string)GetValue(ImageFileProperty); }
set{ SetValue(ImageFileProperty, value); }
}
You can't to set bind to this property you've just create in your constructor. This will be used when you're consuming the class (in xaml or c#).
Now you have to use your property to set the image where you wanna show. I think you should have a variable or private property in this class that is of Image type, for example 'image', so, inside the constructor you should do
image = new Image();
image.BindingContext = this;
image.SetBinding(Image.Source, nameof(ImageFile));
Let me know if I missunderstood, please.
I hope that helps you.
I have a Template Control which has a StackPanel as it's root. What I want is a way to make something like this:
<Controls:MyControl Kind="SomeKind"/>
And based on SomeKind the StackPanel's background would change. There would be a limited number of "Kinds", the same way there is a limited number of HorizontalAlignments in a Button, for example.
<Button HorizontalAlignment="Center"/>
I've searched in the internet a bit and it does seems like it involves Attached Properties, but I haven't found a clean, simple and easy-to-follow example for UWP. I've found some examples for WPF but they doesn't seem to work.
No you don't need attached properties since it's likely to be only associated within your custom control. What you need is a dependency property of an enum type.
Say if you have this enum -
public enum PanelBackgroundType
{
Orange,
Pink,
Offwhite
}
Then your dependency property will look something like this -
public PanelBackgroundType PanelBackgroundType
{
get { return (PanelBackgroundType)GetValue(PanelBackgroundTypeProperty); }
set { SetValue(PanelBackgroundTypeProperty, value); }
}
// Using a DependencyProperty as the backing store for PanelBackgroundType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PanelBackgroundTypeProperty =
DependencyProperty.Register("PanelBackgroundType", typeof(PanelBackgroundType), typeof(MyControl),
new PropertyMetadata(PanelBackgroundType.Offwhite, (s, e) =>
{
if ((PanelBackgroundType)e.NewValue != (PanelBackgroundType)e.OldValue)
{
// value really changed, invoke your changed logic here
var control = (MyControl)s;
switch ((PanelBackgroundType)(e.NewValue))
{
case PanelBackgroundType.Orange:
control.MyStackPanel.Background = new SolidColorBrush(Colors.Orange);
break;
case PanelBackgroundType.Pink:
control.MyStackPanel.Background = new SolidColorBrush(Colors.Pink);
break;
case PanelBackgroundType.Offwhite:
default:
control.MyStackPanel.Background = new SolidColorBrush(Colors.Wheat);
break;
}
}
else
{
// else this was invoked because of boxing, do nothing
}
}));
Note that I have a check (PanelBackgroundType)e.NewValue != (PanelBackgroundType)e.OldValue inside the property changed callback to see if the value of the dp really has changed. This may seem redundant but according to MSDN, this is the best practise as -
If the type of a DependencyProperty is an enumeration or a structure,
the callback may be invoked even if the internal values of the
structure or the enumeration value did not change. This is different
from a system primitive such as a string where it only is invoked if
the value changed. This is a side effect of box and unbox operations
on these values that is done internally. If you have a
PropertyChangedCallback method for a property where your value is an
enumeration or structure, you need to compare the OldValue and
NewValue by casting the values yourself and using the overloaded
comparison operators that are available to the now-cast values. Or, if
no such operator is available (which might be the case for a custom
structure), you may need to compare the individual values. You would
typically choose to do nothing if the result is that the values have
not changed.
Have a look at this link: https://msdn.microsoft.com/en-us/windows/uwp/xaml-platform/custom-dependency-properties
It will show you how to add custom dependency properties for an object that you'll be able to edit in the UI.
I'll give you a quick example of what you want event though I recommend you to take a look at Microsoft's docs
public sealed partial class MyControl : UserControl
{
public MyControl()
{
this.InitializeComponent();
}
public static readonly DependencyProperty KindProperty = DependencyProperty.Register(
"Kind", typeof(string), typeof(MyControl),
new PropertyMetadata(null, new PropertyChangedCallback(OnKindChanged)));
public string Kind
{
get { return (string)GetValue(KindProperty); }
set { SetValue(KindProperty, value); }
}
private static void OnKindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Executed method when the KindProperty is changed
}
}
I have a simple attached property:
class TestAttached
{
public static readonly DependencyProperty TestProperty = DependencyProperty.RegisterAttached("TestProperty", typeof(string), typeof(TestAttached));
public static string GetTest(DependencyObject d)
{
return (string)d.GetValue(TestProperty);
}
public static void SetTest(DependencyObject d, string value)
{
d.SetValue(TestProperty, value);
TextBox tb = d as TextBox;
tb.Text = value;
}
}
and
<TextBox local:TestAttached.Test="Test" />
Nothing happens and no breakpoint is hit (which I put at SetTest) unless I set the first parameter of RegisterAttached to "TestProperty" instead of "Test". No tutorial I have found, including the one on MSDN, does this, and as far as I can tell, my code is the same as theirs and should work. What gives?
When Dependency property is get/set via XAML, wrapper method never gets called. So, you should avoid writing code there.
From MSDN:
Current WPF implementation of the XAML processor behavior for property
setting bypasses the wrappers entirely, you should not put any
additional logic into the set definitions of the wrapper for your
custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
Instead you can use PropertyChangedCallback in case you want to put some code on property change of DP.
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("Test", typeof(string),
typeof(TestAttached),
new PropertyMetadata(TestChanged));
public static void TestChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// Place your code here
}
UPDATE
But if I change the name to "TestProperty" then the breakpoint gets
hit at SetTest.
Reason for that is: Test now behaves as normal CLR property. Property is nothing but instead Get/Set methods when you crack it down to IL code.
That's why setter gets hit like it does for normal CLR property.
If you try to bind with some other property,
<TextBox local:TestAttached.Test="{Binding SomeCLRProperty}" />
you will see application crashes stating:
A 'Binding' cannot be set on the 'SetTest' property of type 'TextBox'.
A 'Binding' can only be set on a DependencyProperty of a
DependencyObject.
I created a control, derived from Canvas, that should plot a live diagram, given values that are passed via a binding to a DependencyProperty. The simplified version is this:
public class Plotter : Canvas
{
public float Value { get { return (float)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(float), typeof(Plotter),
new PropertyMetadata(0f, new PropertyChangedCallback(ValueChangedCallBack)));
public static void ValueChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
Plotter plotter = (Plotter)property;
plotter.Value = (float)args.NewValue; //<-- Removed this line to get it to work
// Actually draw the value into the canvas geometry
plotter.PlotValue(plotter.Value);
}
}
I bound the control like this:
<mystuff:Plotter Value="{Binding MyViewModelProperty}" Height="50" Width="200" />
My ViewModel implements INotifyPropertyChanged and calls PropertyChanged correctly. If I bind MyViewModelProperty to a textbox, it correctly updates every time. Only if I bind it to my own control, my ValueChangedCallBack is only called once as the page is loaded, and then never again.
What am I not seeing here? Thanks for any help!
Solved: I dont have to set the Value explicitly in the callback.
You set the property Value on the callback for the property Value changing. That doesn't make much sense in any case. But is that locally set value overriding the binding value, causing your binding to no longer be set on the dependency property?
Do you need to set the Mode of your binding to TwoWay?
Should you not be using DependencyProperty.Register instead of DependencyProperty.RegisterAttached?
Would it be possible for an Adorner to be hidden / displayed depending on the value of a property in a class ?
Should I use attached properties for this purpose ?
If so, how exactly can the Adorner's visibility be controlled; do I have to manually remove it / add it to the Adorner Layer within the Dependency Object's OnChanged event ?
This is just a very quick code representation of what I'm trying to do:
(Note: I'm not even sure if its the right way of doing things. I want the Adorner's visibility to be controlled by the value of a property that is modified by the code in my business model. The problem with Attached Properties is that its the control's responsibility to update the value of the property instead of the code in my business domain.)
public static class IsValidBehavior
{
public static readonly DependencyProperty IsValidProperty = DependencyProperty.RegisterAttached("IsValid",
typeof(bool),
typeof(IsValidBehavior),
new UIPropertyMetadata(false, OnIsValidChanged));
public static bool GetIsValid(DependencyObject obj)
{
return (bool)obj.GetValue(IsValidProperty);
}
public static void SetIsValid(DependencyObject obj, bool value)
{
obj.SetValue(IsValidProperty, value);
}
private static void OnIsValidChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement element = dependencyObject as UIElement;
if (element == null)
return;
if ((bool)e.NewValue == true)
{
// Display the Adorner
}
else
{
// Hide the Adorner
}
}
}
Well, if I right understood your question, in WPF you have 2 ways to do that, from code or from XAML. From code, you more or less already did, in XAML you can do something like this, I think:
Visibility="{Binding Path=MyVisibilityVariant,
Converter={StaticResource VisibilityConverter}}
In other words bind it to some property. My general suggestion: is use XAML whenever you can, considering a couple of variants:
XAML declaration makes the software very scallable, but also more complex (consider your, or your group cappabilities, somehow doing the stuff in code behind is best, if not only solution available)
Consider you deadlines, cause on XAML stuff implementing/debugging/fixing you will spend more time then on code.
EDIT
Defining custom Adorder in order to be able to define it in XAML