Changing standard property into a DependencyProperty - c#

In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?

I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}

Related

PropertyChangedCallback not triggered when DependencyObject Class Member is Modified

I created a DependencyObject with properties in it, I also have it inherit INotifyPropertyChanged.
But when it is implemented as a DependencyProperty, it does not trigger PropertyChangedCallback when I change a single property from the DependencyObject both in Design and Code.
This is the Dependency Object that I will use for my CustomControl.
public class Basemap : DependencyObject, INotifyPropertyChanged
{
private string identifier;
private string name;
private string alias;
private string url;
private Map.DetailRange detailrange;
private Map.Attribution attribution;
public string Identifier
{
get => identifier;
set
{
identifier = value;
OnPropertyChanged("Identifier");
}
}
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string Alias
{
get => alias;
set
{
alias = value;
OnPropertyChanged("Alias");
}
}
public string URL
{
get => url;
set
{
url = value;
OnPropertyChanged("URL");
}
}
public Map.DetailRange DetailRange
{
get => detailrange;
set
{
detailrange = value;
OnPropertyChanged("DetailRange");
}
}
public Map.Attribution Attribution
{
get => attribution;
set
{
attribution = value;
OnPropertyChanged("Attribution");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
It has a PropertyChangedEventHandler and Invoking it whenever the OnPropertyChanged is called, just like in this GUIDE
This it the DependecyProperty I implimented into the CustomControl that has a PropertyChangedCallback.
public static DependencyProperty BasemapProperty = DependencyProperty.Register("Basemap", typeof(Basemap), typeof(Browser), new PropertyMetadata(null, BasemapChanged));
private static void BasemapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Basemap Changed");
}
But the PropertyChangedCallback is not triggered when an individual property's value is changed, but it is triggered only when the whole DependecyObject is updated or inserted. Both in Design and Code
<Window.Resources>
<mMAP:Basemap x:Key="OpenStreetMap"
Identifier="OpenStreetMap.Standard"
Name="OpenStreetMap Standard"
Alias="OpenStreetMap"
URL="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
DetailRange="0,0,18"
Attribution="© OpenStreetMap contributors, CC-BY-SA" />
</Window.Resources>
Can anyone suggest any fixes to this?
Thank you.
Edit:
I assignend the PropertyChanged event from the DependecyObject to the control. Basemap.PropertyChanged += Basemap_PropertyChanged; so it can be triggered every time any member of the DependecyObject class. It may be sacrilegious to do it to WPF, but works for me.
PS:
I needed it to be grouped, for it is a configuration for the CustomControl.
You are not binding to the Dependency Property you defined in the control, that is why the PropertyChangedCallback BasemapChanged is not triggered.
While it works on some cases, I try to avoid implementing the INotifyPropertyChanged Interface on Controls and use only Dependency Properties instead when binding is required.
public static DependencyProperty AliasProperty = DependencyProperty.Register(
nameof(Alias),
typeof(string),
typeof(Basemap),
new PropertyMetadata(null, AliasPropertyChangedCallback));
public string Alias
{
get { return (string)GetValue(Alias); }
set { SetValue(Alias, value); }
}
private static void AliasPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine($"New Value of Alias: {e.NewValue}");
}
See: Dependency properties overview (WPF .NET)

How to binding User Control in MVVM Caliburn.Micro?

I have a User Control(next UC) with label. I need on button click change a UC label content. On UC codebehind i create DependencyProperty and methods to change a label.
public string InfoLabel
{
get
{
return (string)this.GetValue(InfoLabelProperty);
}
set
{
this.SetValue(InfoLabelProperty, value);
}
}
private static void InfoLabelChangeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
uc.CInfoLabel.Content = uc.InfoLabel;
}
public static readonly DependencyProperty InfoLabelProperty = DependencyProperty.Register("InfoLabel", typeof(string), typeof(UserControl1), new PropertyMetadata("", new PropertyChangedCallback(InfoLabelChangeCallback)));
On ShellView i got Binding on control and button.
<c:UserControl1 InfoLabel="{Binding InfoLabel1}" />
<Button x:Name="ChangeUserControllButton"/>
On ShellViewModel I have Binding InfoLabel1.
private string infoLabel= "something";
public string InfoLabel1
{
get
{
return infoLabel;
}
set
{
infoLabel = value;
}
}
public void ChangeUserControllButton()
{
InfoLabel1 = "Hello world";
}
The problem is When a UC is initialize, then it`s work. I mean label from UC will have content "something", but when I Click on button, content not changing to "Hello world". How to make it right?
View model needs to implement INotifyPropertyChanged so as to be able to notify UI that it should refresh/update because the bound model has changed. I believe that there is already a base class that provides that functionality.
Reference Caliburn.Micro.PropertyChangedBase
Update ShellViewModel to be derived from PropertyChangedBase and then in property call one of the available methods that would allow your view model to notify UI of property changed.
public class ShellViewModel : PropertyChangedBase {
private string infoLabel= "something";
public string InfoLabel1 {
get {
return infoLabel;
}
set {
infoLabel = value;
NotifyOfPropertyChange();
//Or
//Set(ref infoLabel, value);
}
}
public void ChangeUserControllButton() {
InfoLabel1 = "Hello world";
}
}
Read more at https://caliburnmicro.com/ to get examples of how to use the framework.

Binding DependencyProperty in custom control to ViewModel property

I'm using MVVMCross in my crossplatform native Xamarin app. I seem to have a problem binding boolean properties in my custom control to boolean properties in my ViewModel. For example:
My custom control BarCodeTextBox.cs:
public sealed class BarCodeTextBox : TextBox
{
public BarCodeTextBox()
{
this.DefaultStyleKey = typeof(BarCodeTextBox);
}
public bool IsListeningForCodes
{
get { return (bool)GetValue(IsListeningForCodesProperty); }
set {
SetValue(IsListeningForCodesProperty, value);
if (value)
{
IsReadOnly = true;
PrefixElement.Visibility = Visibility.Collapsed;
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
}
else
{
IsReadOnly = false;
Focus(FocusState.Keyboard);
PrefixElement.Visibility = Visibility.Visible;
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
}
Text = string.Empty;
}
}
// Using a DependencyProperty as the backing store for IsListening. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsListeningForCodesProperty =
DependencyProperty.Register("IsListeningForCodes", typeof(bool), typeof(BarCodeTextBox), new PropertyMetadata(false));
The viewmodel of the page BoxCloseViewModel.cs:
public class BoxCloseViewModel : ViewModelBase
{
private bool m_isManualBoxCodeInput;
public bool IsManualBoxCodeInput
{
get { return m_isManualBoxCodeInput; }
set { SetProperty(ref m_isManualBoxCodeInput, value); }
}
}
The binding in BoxCloseView.xaml:
<Controls:BarCodeTextBox x:Name="BarCodeInput" Text="{Binding BoxCode, Mode=TwoWay}" IsListeningForCodes="{Binding IsManualBoxCodeInput, Mode=OneWay, Converter={StaticResource BoolToInverseBool}}"/>
When I change the value of IsManualBoxCodeInput in the ViewModel nothing happens in the control (IsListeningForCodes does not change). Things I checked:
The converter works perfectly (and removing it does not solve the issue). In fact the converter is called when the ViewModel property changes (I'm able to debug it).
When I change the value of IsListeningForCodes in the page's code behind, it works (the control shows the change).
When I do the exact same thing with a string property, everything works perfectly.
There are no binding errors in the Output log.
PropertyChangedEvent is fired correctly with IsManualBoxCodeInput property.
I've realized the same thing happened to another control with a boolean property which used to work, after migrating to MVVMCross no longer does.
Bindings don't call the setter property of a DependencyObject. If you want some code to execute when a binding changes you need to add it as a callback of the PropertyMetadata.
public bool IsListeningForCodes
{
get { return (bool)GetValue(IsListeningForCodesProperty); }
set { SetValue(IsListeningForCodesProperty, value); }
}
public static readonly DependencyProperty IsListeningForCodesProperty =
DependencyProperty.Register("IsListeningForCodes", typeof(bool), typeof(BarCodeTextBox),
new PropertyMetadata(false, OnIsListeningForCodesChanged));
static void OnIsListeningForCodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (BarCodeTextBox)d;
instance.OnIsListeningForCodesChanged();
}
void OnIsListeningForCodesChanged()
{
if (IsListeningForCodes)
{
IsReadOnly = true;
PrefixElement.Visibility = Visibility.Collapsed;
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
}
else
{
IsReadOnly = false;
Focus(FocusState.Keyboard);
PrefixElement.Visibility = Visibility.Visible;
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
}
Text = string.Empty;
}

A custom attribute for setting whether a textbox is editable

I am trying to setup a custom attribute in C# to set whether a business object property is editable, eventually enabling or disabling ReadOnly a textbox in XAML. Since (I thought) IsEditable was already implemented in System.Windows.Controls, i thought this would work:
[AttributeUsage(AttributeTargets.Property)]
public class EditableAttribute : Attribute
{
public EditableAttribute(bool isEditable)
{
this.ReadOnly = !isEditable;
}
public virtual bool ReadOnly { get; set; }
}
Well, go figure, it doesn't. I set [Editable(false)] to a string in an object and it is still editable. I have a feeling I'm not even close. Any help or suggestions would be greatly appreciated!
I am aware this can be setup as a style in xaml, but for this case it needs to be in the business object.
Thanks
You can use BindingDecoratorBase to use custom binding and use an attribute.
The following code is just me modifying my code in my project that uses custom validation . It probably should be refractored.
public interface IEditatble
{
void SetValue(Control sender, DependencyProperty property);
}
[AttributeUsage(AttributeTargets.Property)]
public class EditableAttribute : Attribute, IEditatble
{
public EditableAttribute(bool isEditable)
{
this.ReadOnly = !isEditable;
}
public virtual bool ReadOnly { get; set; }
public void SetValue(System.Windows.Controls.Control sender, System.Windows.DependencyProperty property)
{
sender.SetValue(property, this.ReadOnly);
}
}
You can create a custom binding:
public class ReadonlyBinding : BindingDecoratorBase
{
private DependencyProperty _targetProperty = null;
public ReadonlyBinding()
: base()
{
Binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
}
public override object ProvideValue(IServiceProvider provider)
{
// Get the binding expression
object bindingExpression = base.ProvideValue(provider);
// Bound items
DependencyObject targetObject;
// Try to get the bound items
if (TryGetTargetItems(provider, out targetObject, out _targetProperty))
{
if (targetObject is FrameworkElement)
{
// Get the element and implement datacontext changes
FrameworkElement element = targetObject as FrameworkElement;
element.DataContextChanged += new DependencyPropertyChangedEventHandler(element_DataContextChanged);
}
}
// Go on with the flow
return bindingExpression;
}
void element_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
object datacontext = e.NewValue;
if (datacontext != null && _targetProperty != null)
{
PropertyInfo property = datacontext.GetType().GetProperty(Binding.Path.Path);
if (property != null)
{
var attribute = property.GetCustomAttributes(true).Where(o => o is IEditatble).FirstOrDefault();
if (attribute != null)
{
Control cntrl = sender as Control;
((IEditatble)attribute).SetValue(cntrl, _targetProperty);
}
}
}
}
}
And you can use it like:
[Editable(true)]
public string Name { get; set; }
Xaml:
<TextBox IsReadOnly="{local:ReadonlyBinding Path=Name}" />
For your EditableAttribute to work, TextBox classes should use reflection on your model to check whether the attribute is set and set necessary properties. What I'm trying to say is that attribute is no more than metadata and it doesn't control the application workflow unless the application wishes so.
You could inherit from basic TextBox and insert necessary functionality though it is an overkill. You should just declare IsSomePropertyReadOnly variable and bind to it in TextBox.
Though if you're feeling really fancy, you could write some wrapper class like
public class ReadOrWriteText<T>
{
private T _value;
bool IsReadOnly { get; set; }
public T Value
{
get { return _value; }
set { if (IsReadOnly) return; _value = value; }
}
}
and bind to it's IsReadOnly and Value properties. Though it is an overkill also.

DataContext, DependencyProperties and Bindings

I have a UserControl, we'll call it "Header". It has a DependencyProperty called ProjectID, this control has a View Model and I set it to be the DataContext:
public BillingInfoHeaderControlVM VM
{
get
{
return (BillingInfoHeaderControlVM)DataContext;
}
set
{
DataContext = value;
}
}
public static readonly DependencyProperty ProjectIDProperty =
DependencyProperty.Register("ProjectID", typeof(int), typeof(BillingInfoHeaderControl), new PropertyMetadata();
public int ProjectID
{
set
{
SetValue(ProjectIDProperty, value);
}
get
{
return (int)GetValue(ProjectIDProperty);
}
}
Now what I want to do, is to bind the ProjectID of a control to this control's ProjectID:
<controls:Header Grid.Row ="0" x:Name="Header" ProjectID="{Binding ProjectID, Mode=OneWay}"></controls:Header>
Now when I run this, I get an error in the InitializeControl() method that states "
Property Get method was not found.
From what I'm reading, I'm seeing this is because the Binding ProjectID is relative to the data context of the control. Of course I could set the ElementName within the binding:
<controls:Header Grid.Row ="0" x:Name="Header" ProjectID="{Binding ProjectID, Mode=OneWay, ElementName=ParentControl}"></controls:Header>
But this is ugly, and to be honest we don't want to have to remember to do this for this control whenever we use it. What other options do I have? Is there a way to set the source of the binding to use the DataContext of the parent?
I duplicated your concept in code and it compiles and runs fine.
I have included the control code and the viewmodel below in case you are doing something different.
*Note: I kept the viewmodel ProjectID as a simple update property.:
namespace Demo1
{
public partial class BillingInfoHeaderControl : UserControl
{
public BillingInfoHeaderControl()
{
InitializeComponent();
this.DataContext = new BillingInfoHeaderControlVM();
}
public int ProjectId
{
get { return (int)GetValue(ProjectIdProperty); }
set { SetValue(ProjectIdProperty, value); }
}
public static readonly DependencyProperty ProjectIdProperty =
DependencyProperty.Register("ProjectId", typeof(int), typeof(BillingInfoHeaderControl),
new PropertyMetadata(0));
}
}
namespace Demo1
{
public class BillingInfoHeaderControlVM : INotifyPropertyChanged
{
private int _projectId;
public int ProjectId
{
get { return _projectId; }
set
{
if (_projectId != value)
{
_projectId = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProjectId"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

Categories

Resources