I'm working on a custom controller for a Windows 8 store app in C#.
I added some DependencyProperties; some simple ones (like Radius below) and a collection of custom items used to construct and draw various shapes (NinjaSource).
<StackPanel>
<cc:NinjaControl Margin="120,0,0,0" NinjaSource="{Binding NinjaCollection}" Radius="45"/>
</StackPanel>
The collection looks like this
public ObservableCollection<Ninja> NinjaCollection{ get; set; }
And the Ninja class basically has some properties and implements INotifyPropertyChanged
public class Ninja : INotifyPropertyChanged
{
private string _name;
private double _value;
private Path _path;
private bool _showName;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
...
Whenever a simple property, like Radius, is changed, my custom control picks it up and redraws like this:
public sealed partial class NinjaControl: UserControl
{
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof (double), typeof (NinjaControl),
new PropertyMetadata(default(double), PropertyChangedCallback));
...
private static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var instance = o as NinjaControl;
if (instance == null) return;
instance.RedrawMyControl();
}
This works great, I can bind Radius to whatever I want and the PropertyChangedCallback is called whenever it changes.
I want the same thing to occur whenever any values inside the NinjaCollection change.
I have a DependencyProperty registered for the actual collection, with a property wrapper, but I believe it will only look at changes to the actual collection and not the values within.
public static readonly DependencyProperty NinjaSourceProperty =
DependencyProperty.Register("NinjaSource", typeof(ObservableCollection<Ninja>), typeof(NinjaControl), new PropertyMetadata(new ObservableCollection<Ninja>(), PropertyChangedCallback));
Any help appreciated.
what you want to do is instead of exposing ObservableCollection as NinjaSource. Create a custom object that contains the observable collection. Expose specific Add Remove methods which will allow you to then raise events.
Related
I have this user control codebehind:
public partial class MyControl : UserControl, INotifyPropertyChanged
{
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set
{
SetValue(MyPropertyProperty, value);
PropertyChanged?.Invoke(null, null);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(string));
public MyControl()
{
InitializeComponent();
}
}
XAML:
<Grid>
<TextBox Text="{Binding MyProperty,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
VerticalAlignment="Center"
Height="20"
Margin="5"/>
</Grid>
I have one of those controls in my MainWindow and when I put a breakpoint on the "SetValue" line and change the value of the TextBox the breakpoint is hit and everything is right with the world. If I change the DP registering to:
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl));
That breakpoint is no longer hit even though nothing else has been changed and afaik this is the better, correct way to register the DP.
Why is this happening and how to fix it?
Why is this happening and how to fix it?
Because you are violating WPF conventions.
And the XAML constructor (compiler) cannot guess how you are breaking this convention.
In fact, in the first option, you create a regular CLR property.
And, if you try to set a binding for it, you will most likely get a compilation error.
By convention, by which the XAML compiler works, the CLR wrapper should not contain ANY LOGIC, except for calling GetValue () and SetValue () and the owner of the property should be the class in which they are declared.
In this case, the compiler can find the original DependecyProperty and when setting / getting values it will use it without calling the CLR wrapper of the property.
And the CLR wrapper is designed for the convenience of the programmer when "hand-coding" in Sharp.
The INotifyPropertyChanged interface also looks completely pointless.
DependecyProperty has its own change notification mechanism and you don't need to duplicate it by calling PropertyChanged.
If you need to track changes in the DependecyProperty value, you must do this in the callback method specified when declaring DependecyProperty.
public partial class MyControl : UserControl
{
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl), new PropertyMetadata(string.Empty, MyPropertyChanged));
private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Here the logic for handling value change
MyControl myControl = (MyControl)d;
// Some Code
}
public MyControl()
{
InitializeComponent();
}
}
How would I achieve the same with the MyPropertyChanged callback?
This is very wrong, but if you need to, you can raise PropertyChanged in the callback method as well.
public partial class MyControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl), new PropertyMetadata(string.Empty, MyPropertyChanged));
private static readonly PropertyChangedEventArgs MyPropertyPropertyChangedArgs = new PropertyChangedEventArgs(nameof(MyProperty));
private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Here the logic for handling value change
MyControl myControl = (MyControl)d;
myControl.PropertyChanged?.Invoke(myControl, MyPropertyPropertyChangedArgs);
// Some Code
}
public MyControl()
{
InitializeComponent();
}
}
What would be the proper way to notify the ViewModel of the window the user control is in that the value of "MyProperty" has changed when the user types text in the TextBox?
If the VM needs to get the changed value of the MyProperty property, you need to set a binding to this property at the place where your control is used.
And already in the VM itself, process the change in its properties.
Usually, there is a corresponding method in a base class implementation for a VM.
But if there is no such method, then you can use the setter of this property.
Example:
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(
nameof(MyProperty),
typeof(string),
typeof(MyControl),
new FrameworkPropertyMetadata(string.Empty, MyPropertyChanged) { BindsTwoWayByDefault = true });
<local:MyControl MyProperty="{Binding VmProperty}" .../>
public class MainViewModel: ....
{
public string VmProperty
{
get => _vmProperty;
set
{
if(Set(ref _vmProperty, value))
{
// Some code to handle value change.
}
}
}
}
Additional advice.
Your given implementation is not very suitable for UserControl.
You'd better change it to Custom Control.
I have in my C# WPF solution as follows:
Mainwindow with a startupControl (always running)
Dialogwindow with diffent other controls.
A public Helper-class containing some public static properties to indicate what department at customer is active, and for who i have focus on at the moment.
I want simply two XAML textBlocks displayed in my Startupcontrol to show the property names if and when the value for a department or costumer has been set.
I think it could properbly work smooth with some sort of binding, but i dont know anything about bindings, other than they exists.
Is it possible in any way from my controls in my dialogwindow, to change the value of the 2 textblocks in the Startupcontrol ?
As the program is small and I know exactly when the values change, I think i could make a function setting the value ex.:
activeDepartmentTextBlock.Text = HelperClass.ActiveDepartment.Name;
But from my control.cs in the DialogWindow, it seems to be possible to reach the activeDepartmentTextBlock.
Anyone who can help me ?
Since WPF 4.5, binding to static properties with property change notification is quite simple.
The example below assumes that you want to notify about the change of the ActiveDepartment property of the HelperClass (and not about the Name property of the Department object). In addition to the static property, declare a static event named StaticPropertyChanged and fire it when the static property changes:
public class Department
{
public string Name { get; set; }
}
public class HelperClass
{
public static event PropertyChangedEventHandler StaticPropertyChanged;
private static Department activeDepartment;
public static Department ActiveDepartment
{
get => activeDepartment;
set
{
activeDepartment = value;
StaticPropertyChanged?.Invoke(null,
new PropertyChangedEventArgs(nameof(ActiveDepartment)));
}
}
}
You can bind to a static property like this:
<TextBlock Text="{Binding Path=(local:HelperClass.ActiveDepartment).Name}"/>
Binding is a good solution but you have static property so you can't use binding infrastructure directly to get notified of updates since there's no DependencyObject (or object instance that implement INotifyPropertyChanged) involved.
If the value does change and you need to update TextBlock's value in main window yo can create a singleton instead of static class to contain the value and bind to that.
An example of the singleton:
public class HelperClass : DependencyObject {
public static readonly DependencyProperty ActiveDepartmentProperty =
DependencyProperty.Register( "ActiveDepartment", typeof( Department ),
typeof( HelperClass ), new UIPropertyMetadata( "" ) );
public Department ActiveDepartment {
get { return (Department) GetValue( ActiveDepartmentProperty ); }
set { SetValue( ActiveDepartmentProperty, value ); }
}
public static HelperClass Instance { get; private set; }
static HelperClass() {
Instance = new HelperClass();
}
}
So binding will work like in an example below:
<TextBox Text="{Binding Source={x:Static local:HelperClass.Instance}, Path=ActiveDepartment.Name}"/>
It might look like a hard way and that’s it. You can use events model instead and add the event to your HelperClass. MainWindow can add event handler and change activeDepartmentTextBlock value when event raised.
public MainWindow()
{
InitializeComponent();
HelperClass.Instance.DepartmentChanged += OnDepartmentChanged;
}
private void OnDepartmentChanged(Department newDepartment)
{
activeDepartmentTextBlock.Text = newDepartment.Name;
}
Update. If you want to have the simplest solution you can break encapsulation principle and pass MainWindow as a parameter to DialogWindow and make activeDepartmentTextBlock public. So you will be able to save the link to the MainWindow in the DialogWindow's field and just change the text when you need in DialogWindow:
this.mainWindow.activeDepartmentTextBlock.Text = HelperClass.ActiveDepartment.Name;
I'd first like to say I'm very new to Binding.. I've done some things in WPF already but I never used binding because concept is a bit too hard to understand for me right of the bat. Even this what I'm doing now is something i managed to salvage from a tutorial that I didn't fully understand.
In my application I have a static class with static properties and there's a static method that changes those static properties.
Example:
public static class AppStyle
{
public static SolidColorBrush property = Brushes.Red;
public static void ChangeTheme()
{
property = Brushes.Blue;
}
}
Inside the XAML I have a control that has it's background binded to this value. I even declared the namespace properly.
...
xmlns:style="clr-namespace:CorrectNamespace;assembly=RightAssembly"
...
<TextBox x:Name="TXT_PN"
Background="{Binding Source={x:Static style:AppStyle.property}}"
TextChanged="TXT_PN_TextChanged"
Text="Text"/>
When the application loads it will load the correct setting (Red color) however when things change and ChangeTheme() is called, the static class will get the new value, however the textbox's Background will not change.
What am I doing wrong here? As I said, I'm very new to this and I would appreciate the solution in laymen's terms.
Thank you!
First of all, your property is actually not a property, but a field. A minimal property declaration would look like this:
public static SolidColorBrush Property { get; set; }
Please note the name is starting with an uppercase letter, which is a widely accepted coding convention in C#.
Because you also want to have a change notification fired whenever the value of the property changes, you need to declare a property-changed event (which for non-static properties is usually done by implementing the INotifyPropertyChanged interface).
For static properties there is a new mechanism in WPF 4.5 (or 4.0?), where you can write a static property changed event and property declaration like this:
public static class AppStyle
{
public static event PropertyChangedEventHandler StaticPropertyChanged;
private static void OnStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
private static SolidColorBrush property = Brushes.Red; // backing field
public static SolidColorBrush Property
{
get { return property; }
set
{
property = value;
OnStaticPropertyChanged("Property");
}
}
public static void ChangeTheme()
{
Property = Brushes.Blue;
}
}
The binding to a static property would be written with the property path in parentheses:
Background="{Binding Path=(style:AppStyle.Property)}"
To implement reaction on a change, you need to notify about the change. See INotifyPropertyChanged interface. However, you can't use it with a static class. What about a singleton (ideally using some dependency injection container) instead of a static class?
I find this question during code reading. After search MSDN, it has same issue too.
http://msdn.microsoft.com/en-us/library/ms597501.aspx
For DependencyProperty.Register method, it has code example like:
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(...);
public double CurrentReading
{
get { return (double)GetValue(CurrentReadingProperty); }
set { SetValue(CurrentReadingProperty, value); }
}
For RegisterAttached method http://msdn.microsoft.com/en-us/library/ms597496.aspx , it has code example like:
public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached(....);
public static void SetIsBubbleSource(UIElement element, Boolean value)
{
element.SetValue(IsBubbleSourceProperty, value);
}
public static Boolean GetIsBubbleSource(UIElement element)
{
return (Boolean)element.GetValue(IsBubbleSourceProperty);
}
My question is, RegisterAttached doesn't use property format and use 2 static functions. Why?
This is because RegisterAttached and the associated static methods are for registering attached properties like Canvas.Left, which are defined in one class, but can be set on instances of any other class (derived from DependencyObject). You can for example set Canvas.Left on a Button in code like this:
Canvas.SetLeft(button, 100);
You need a static set method since you cannot add a Left property to class Button.
I've got a UserControl class called A and that one contains a Border Property. Then others classes are inherited from A class, but I cannot use my new Property.
public class A : UserControl
{
public A()
{
Border2 = new Border();
Border2.BorderBrush = Media.Brushes.LightGray;
}
public static readonly DependendyProperty Border2Property = DependencyProperty.Register("Border2", typeof(Border), typeof(A));
public Border Border2
{
get { return (Border)GetValue(Border2Property); }
set { SetValue(Border2Property, value); }
}
}
Then when I use another class where is inherited from A, I cannot use this Border2 Property, I'm writing something like:
<local:A.Border2></...
But it tells me that Border2 property doesn't support values of type Grid.
That's because you've created a standard dependency property. If you want to be able to set it on other types besides A, then you want to create an attached property instead. This only takes a handful of code changes:
Register it by calling DependencyProperty.RegisterAttached (instead of .Register)
Add static GetBorder2 and SetBorder2 methods to class A. Even if your code doesn't call these methods, they're part of the pattern and need to be there -- they're how you tell the compiler that yes, you do intend for people to be able to set this attached property in XAML.
For example:
public static readonly DependencyProperty Border2Property =
DependencyProperty.RegisterAttached("Border2", typeof(Border), typeof(A));
public static Border GetBorder2(DependencyObject obj)
{
return (Border) obj.GetValue(Border2Property);
}
public static void SetBorder2(DependencyObject obj, Border2 value)
{
obj.SetValue(Border2Property, value);
}
If your property should only be available for certain element types -- e.g. if it should only apply to FrameworkElement and its descendants, or to Panel and its descendants, or something like that -- then use that as the type of the first parameter to GetBorder2 and SetBorder2.