I have a control where i want to add CharacterCasing since it doesn't support it by default.
I added a custom dependency property called "CharacterCasing".
Now when i use it in xaml i want to have the options just like in the normal TextBox:
Any ideas how to implement the suggestion list in the dependency property?
This is my code in xaml:
<TestControl:APTextBox CharacterCasing="UpperCase" Text="{Binding AktuelleZeile.LKR, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}">
And this is the dependency property:
public static readonly DependencyProperty CharacterCasingProperty =
DependencyProperty.Register(name: "CharacterCasing",
propertyType: typeof(string),
ownerType: typeof(APTextBox),
typeMetadata: new PropertyMetadata("Normal"));
public string CharacterCasing
{
get { return (string)this.GetValue(CharacterCasingProperty); }
set { this.SetValue(CharacterCasingProperty, value); }
}
Because you define it as string. Change the string with CharacterCasing or which kind of enum you want to use.
public static readonly DependencyProperty CharacterCasingProperty =
DependencyProperty.Register(name: "CharacterCasing",
propertyType: typeof(CharacterCasing),
ownerType: typeof(APTextBox),
typeMetadata: new PropertyMetadata(CharacterCasing.Normal));
public CharacterCasing CharacterCasing
{
get { return (CharacterCasing)this.GetValue(CharacterCasingProperty); }
set { this.SetValue(CharacterCasingProperty, value); }
}
I have EntitiesUserControl responsible for EntitiesCount dependency property:
public static readonly DependencyProperty EntitiesCountProperty = DependencyProperty.Register(
nameof(EntitiesCount),
typeof(int),
typeof(EntitiesUserControl),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public int EntitiesCount
{
get { return (int)this.GetValue(EntitiesCountProperty); }
set { this.SetValue(EntitiesCountProperty, value); }
}
Another (primary) control include EntitiesUserControl and read it property through binding:
<controls:EntitiesUserControl EntitiesCount="{Binding CountOfEntities, Mode=OneWayToSource}" />
CountOfEntities property in view model just store and process changing of count value:
private int countOfEntities;
public int CountOfEntities
{
protected get { return this.countOfEntities; }
set
{
this.countOfEntities = value;
// Custom logic with new value...
}
}
I need EntitiesCount property of EntitiesUserControl to be read-only (primary control must not change it, just read) and it works this way only because Mode=OneWayToSource declared explicitly. But if declare TwoWay mode or don't explicitly declare mode, then EntitiesCount could be rewritten from outside (at least right after binding initialization, because it happens after default dependency property value assigned).
I can't do 'legal' read-only dependency property due to binding limitations (best described in this answer), so I need to prevent bindings with mode other than OneWayToSource. It would be best to have some OnlyOneWayToSource flag like BindsTwoWayByDefault value in FrameworkPropertyMetadataOptions enumeration...
Any suggestions how to achieve this?
It's a „bit” hacky, but you can create a Binding-derived class and use that instead of Binding:
[MarkupExtensionReturnType(typeof(OneWayToSourceBinding))]
public class OneWayToSourceBinding : Binding
{
public OneWayToSourceBinding()
{
Mode = BindingMode.OneWayToSource;
}
public OneWayToSourceBinding(string path) : base(path)
{
Mode = BindingMode.OneWayToSource;
}
public new BindingMode Mode
{
get { return BindingMode.OneWayToSource; }
set
{
if (value == BindingMode.OneWayToSource)
{
base.Mode = value;
}
}
}
}
In XAML:
<controls:EntitiesUserControl EntitiesCount="{local:OneWayToSourceBinding CountOfEntities}" />
The namespace mapping local might be something else for you.
This OneWayToSourceBinding sets the Mode to OneWayToSource and prevents setting it to anything else.
I have a custom control that I want to style:
It is just a class that inherits from TextBox and another Interface, the interface only adds an extra property.
How can I apply a style to this custom control so that when the read only property is set, the background turns gray?
public class DionysusTextBox : TextBox, IDionysusControl
{
public DionysusTextBox()
{
SetStyle();
}
#region IDionysusControl Members
public bool KeepReadOnlyState
{
get { return (bool)GetValue(KeepReadOnlyStateProperty); }
set { SetValue(KeepReadOnlyStateProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty KeepReadOnlyStateProperty =
DependencyProperty.Register("KeepReadOnlyState", typeof(bool), typeof(DionysusTextBox), new UIPropertyMetadata(true));
#endregion
#region Style
Style styleListBoxItem = new Style(typeof(DionysusTextBox));
Trigger triggerReadonly = new Trigger { Property = DionysusTextBox.IsReadOnlyProperty, Value = true };
private void SetStyle()
{
triggerReadonly.Setters.Add(new Setter(DionysusTextBox.BackgroundProperty, Brushes.Black));
this.Triggers.Add(triggerReadonly);
}
#endregion
}
Above is the code for the entire class, the way I used the style seemed like the appropriate way but when I add this control to the designer I get the following error:
Triggers collection members must be of type EventTrigger.
Can anyone point me in the right direction?
You can redefine dependency properties' default behavior, and in particular, you can define PropertyChangedCallbacks:
public class DionysusTextBox : TextBox, IDionysusControl
{
static DionysusTextBox()
{
//For the IsReadOnly dependency property
IsReadOnlyProperty.OverrideMetadata(
//On the type DionysusTextBox
typeof(DionysusTextBox),
//Redefine default behavior
new FrameworkPropertyMetadata(
//Default value, can also omit this parameter
null,
//When IsReadOnly changed, this is executed
new PropertyChangedCallback(
(dpo, dpce) =>
{
//dpo hold the DionysusTextBox instance on which IsReachOnly changed
//dpce.NewValue hold the new value of IsReadOnly
//Run logic to set the background here, you are on the UI thread.
//Example of setting the BorderBrush from ARGB values:
var dioBox = dpo as DionysusTextBox;
//Should always be true, of course, it's just my OCD ;)
if (dioBox != null)
{
dioBox.BorderBrush =
ColorConverter.ConvertFromString("#FFDDDDDD") as Color?;
}
})));
//For the BorderBrush property
BorderBrushProperty.OverrideMetadata(
//On the type DionysusTextBox
typeof(DionysusTextBox),
//Redefine default behavior
new FrameworkPropertyMetadata(
//Default value
ColorConverter.ConvertFromString("#FFDDDDDD") as Color?));
}
public DionysusTextBox()
{
SetStyle();
}
}
Please be careful: UIPropertyMetadata != FrameworkPropertyMetadata
Consider this scenario, using MVVM:
On my ModelView, I have one property, of type "string", it does notify the change of properties through INotifyPropertyChanged.
In the view, there is (or not) one control, with a DependencyProperty "Notification" of a type which is not a string. That control may or may not change that property depending on facts that only the control knows (neither the ModelView or the View knows about those). That control might even be on other View which may or may not be on the current visual tree.
In the View, I need a bridge between that control's DependencyProperty and the ViewModel's property, so that changing the view property makes the control change its property, and changing the control's DependencyProperty makes the viewmodel's property change its value.
I've got it to work, but I don't think it's an elegant solution. I might be thinking fuzzy these days so I'm asking if there's something obvious that I might have missed.
The obvious way would be either having the ViewModel Property be a DependencyProperty (so it could be bound two ways), however that is not possible right now (plus, it'd break the MVVM pattern, adding view-specific implementations to the viewmodel).
The other obvious way would be binding the Control's DependencyProperty to the ViewModel's property: this works, but just for one view... several properties cannot (or, I don't know how to do it) be bound to the same DependencyProperty: when I set one binding, I lose the other.
Currently this is what I do:
public class BaseViewUserControl : UserControl
{
// Dependency property, bound to the view's property
public string AudioNotification
{
get { return (string)GetValue(AudioNotificationProperty); }
set { SetValue(AudioNotificationProperty, value); }
}
public static readonly DependencyProperty AudioNotificationProperty = DependencyProperty.Register("AudioNotification", typeof(string), typeof(BaseViewUserControl), new FrameworkPropertyMetadata("None", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnAudioNotificationPropertyChanged));
// Dependency property, bound to the control's dependency property
public AudioNotificationType AudioNotificationToControl
{
get { return (AudioNotificationType)GetValue(AudioNotificationToControlProperty); }
set { SetValue(AudioNotificationToControlProperty, value); }
}
public static readonly DependencyProperty AudioNotificationToControlProperty = DependencyProperty.Register("AudioNotificationToControl", typeof(AudioNotificationType), typeof(BaseViewUserControl), new FrameworkPropertyMetadata(AudioNotificationType.None, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, OnAudioNotificationToControlCoerceValue));
// Converter
private static IValueConverter _audioNotificationTypeConverter;
private static IValueConverter AudioNotificationTypeConverter
{
get { return _audioNotificationTypeConverter ?? (_audioNotificationTypeConverter = new AudioNotificationConverter()); }
}
private Binding _audioNotificationBinding;
private bool PrepareAudioNotificationControlBinding()
{
if (_audioNotificationBinding != null) return true;
var b = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
if (b == null) return false;
_audioNotificationBinding = new Binding { Source = b, Mode = BindingMode.TwoWay, Path = new PropertyPath("Notification") };
SetBinding(AudioNotificationToControlProperty, _audioNotificationBinding);
return true;
}
private static void OnAudioNotificationPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (!(source is BaseViewUserControl)) return;
var src = (BaseViewUserControl)source;
if(src.PrepareAudioNotificationControlBinding())
{
var val = AudioNotificationTypeConverter.ConvertValue<AudioNotificationType>(e.NewValue);
src.AudioNotificationToControl = val;
}
}
private static object OnAudioNotificationToControlCoerceValue(DependencyObject source, object basevalue)
{
if (!(source is BaseViewUserControl)) return basevalue;
var src = (BaseViewUserControl)source;
var val = AudioNotificationTypeConverter.ConvertBackValue<string>(basevalue);
src.AudioNotification = val;
return basevalue;
}
public BaseViewUserControl()
{
var ab = new Binding { Path = new PropertyPath("AudibleNotification"), Mode = BindingMode.TwoWay };
SetBinding(AudibleNotificationProperty, ab);
}
}
NOTE: I'm using this for several things, not just for audio notification (that's an example only). Do not rely on the names to give a solution (if any), this needs to be quite generic. Also, any typos come from simplifying the code to the problem (I've removed much code and changed some property names for clarification).
As I said, it works... I just find it quite not-elegant and I'm sure there should be a better solution than this.
Any suggestions will be more than welcome.
Update
Based on Julien's code, I made this Behavior, which does exactly what I wanted. I implemented it using Converter, but for clarity's sake, I ended up doing the conversion on the control itself and using strings to pass variables along (with an undocumented property in the control's if I still want to use the native data type)
public class BridgePropertyBinderBehavior : Behavior<DependencyObject>
{
public static BridgePropertyBinderBehavior PrepareBindingToControl(FrameworkElement sourceView, string viewModelPropertyPath, FrameworkElement targetControl, string controlPropertyPath)
{
var b = new BridgePropertyBinderBehavior();
BindingOperations.SetBinding(b, AProperty, new Binding(viewModelPropertyPath) { Source = sourceView.DataContext, Mode = BindingMode.TwoWay, BindsDirectlyToSource = true, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
BindingOperations.SetBinding(b, BProperty, new Binding(controlPropertyPath) { Source = targetControl, Mode = BindingMode.TwoWay });
Interaction.GetBehaviors(sourceView).Add(b);
return b;
}
public object A { get { return GetValue(AProperty); } set { SetValue(AProperty, value); } }
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnAChanged(e.NewValue)));
public object B { get { return GetValue(BProperty); } set { SetValue(BProperty, value); } }
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(object), typeof(BridgePropertyBinderBehavior), new FrameworkPropertyMetadata(null, (d, e) => ((BridgePropertyBinderBehavior)d).OnBChanged(e.NewValue)));
private void OnAChanged(object value) { B = value; }
private void OnBChanged(object value) { A = value; }
protected override Freezable CreateInstanceCore()
{
return new BridgePropertyBinderBehavior();
}
}
Which I use like this on my view:
var audioNotificationControl = this.FindVisualTreeRoot().TryFindChild<AudioNotification>();
BridgePropertyBinderBehavior.PrepareBindingToControl(this, "AudioNotification", audioNotificationControl, "Notification");
or
<AudioNotification x:Name="Control">
<ia:Interaction.Behaviors>
<BridgePropertyBinderBehavior
A="{Binding Path=Notification, ElementName=Control, Mode=TwoWay}"
B="{Binding Path=AudioNotification, Mode=TwoWay}" />
</ia:Interaction.Behaviors>
</AudioNotification>
I've accepted his answer since it's what got me on the right track, thanks
If I understand you correctly, you need to bind one DP to two sources, one as a source and the other as a target. I actually have a behavior to do that.
The principle of this behavior is quite simple: it uses two dependency properties and makes the data of one (In) flows into the other (Out). Bind In with a one way binding and Out with a one way to source binding and you're done.
public class BindingBehavior : Behavior<DependencyObject> {
public static readonly DependencyProperty InProperty = DependencyProperty.Register(
"In",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null, (d, e) => ((BindingBehavior) d).OnInPropertyChanged(e.NewValue)));
public static readonly DependencyProperty OutProperty = DependencyProperty.Register(
"Out",
typeof(object),
typeof(BindingBehavior),
new FrameworkPropertyMetadata(null));
// Bind OneWay
public object In {
get { return GetValue(InProperty); }
set { SetValue(InProperty, value); }
}
// Bind OneWayToSource
public object Out {
get { return GetValue(OutProperty); }
set { SetValue(OutProperty, value); }
}
private void OnInPropertyChanged(object value) {
Out = value;
}
protected override Freezable CreateInstanceCore() {
return new BindingBehavior();
}
}
This behavior needs a reference to System.Windows.Interactivity from Blend SDK, which you might be familiar with.
Assuming you remove your string property and only keep a AudioNotificationType one named AudtioNotification, the usage should be similar to:
<YourView x:Name="View">
<YourControl x:Name="Control" AudioNotification="{Binding Notification, ElementName=View}>
<i:Interaction.Behaviors>
<BindingBehavior
In="{Binding AudioNotification, ElementName=Control, Mode=OneWay}"
Out="{Binding YourVmProperty, Mode=OneWayToSource, Converter=YourConverter}" />
</i:Interaction.Behaviors>
</YourControl>
</YourView>
You can place the behavior on any element being on the correct name scope for resolving element names and having the view model as the data context.
This looks like it might be a useful time to add a layer of abstraction. I know. Ick. But bear with me.
What if you had a bridge object that an abitrary number of things can bind to that handles the notifications on change. It doesn't even need to be that complex. Just something that implements INotifyPropertyChanged and then has a property (or properties) that release a notification on change. That way, your ViewModel, your View and your control can all bind to the same property on this bridge object and when one of those changes the bridge object's property, all the others will know that it's time to change as well. As long as all the objects are bound two-way, everything should synch just fine.
That's essentially what you've done on your BaseViewUserControl, but encapsulating the behavior in a separate object might provide you flexibility benefits.
So here I come creating a user control. It consists of a treeview dropping down from a combobox.
Actually, there is a button with a control (DropTree) dropping down from its contextmenu. So I have a control DropTree.
public partial class DropTree : UserControl
{
public TreeView TreeView
{ get{return treeView;} }
public DropTree()
{ InitializeComponent(); }
}
to simplify it, I made the TreeView control public, then I have my main control which is called ComboTreeView.
Now I need to represent some treeview properties in it, so I define several dependency properties:
public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboTreeView), new FrameworkPropertyMetadata { Inherits = true, IsNotDataBindable = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
and in constructor it is:
public ComboTreeView()
{
InitializeComponent();
TreeViewControl.SetBinding(TreeView.SelectedItemProperty, new Binding("SelectedItem") { Source = this, Mode = BindingMode.TwoWay });
}
and it all seems ok, until i run it. It crashes saying that SelectedItem cannot be binded to data. I don't understand?
The same goes for ItemsSource and SelectedValue... but only SelectedValuePath property defined this way goes fine.
Can anybody help? Or is there any other way to bind it correctly?
PS: by the way, I need to use DataBinding for ComboTreeView in my code later.
Try to set the Binding on SelectedValue instead of SelectedItem.
TreeView.SelectedItem is a readonly property. You can't set it, whether explicitly or through binding. In order to select a node in a TreeView, you must set the TreeViewItem.IsSelected property to true.