CheckBox Command Behaviors for Silverlight MVVM Pattern - silverlight

I am trying to detect when an item is checked, and which item is checked in a ListBox using Silverlight 4 and the Prism framework. I found this example on creating behaviors, and tried to follow it but nothing is happening in the debugger. I have three questions:
Why isn't my command executing?
How do I determine which item was checked (i.e. pass a command parameter)?
How do I debug this? (i.e. where can I put break points to begin stepping into this)
Here is my code:
View:
<ListBox x:Name="MyListBox" ItemsSource="{Binding PanelItems, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Enabled}" my:Checked.Command="{Binding Check}" />
<TextBlock x:Name="DisplayName" Text="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public MainPageViewModel()
{
_panelItems.Add( new PanelItem
{
Enabled = true,
DisplayName = "Test1"
} );
Check = new DelegateCommand<object>( itemChecked );
}
public void itemChecked( object o )
{
//do some stuff
}
public DelegateCommand<object> Check { get; set; }
Behavior Class
public class CheckedBehavior : CommandBehaviorBase<CheckBox>
{
public CheckedBehavior( CheckBox element )
: base( element )
{
element.Checked +=new RoutedEventHandler(element_Checked);
}
void element_Checked( object sender, RoutedEventArgs e )
{
base.ExecuteCommand();
}
}
Command Class
public static class Checked
{
public static ICommand GetCommand( DependencyObject obj )
{
return (ICommand) obj.GetValue( CommandProperty );
}
public static void SetCommand( DependencyObject obj, ICommand value )
{
obj.SetValue( CommandProperty, value );
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached( "Command", typeof( CheckBox ), typeof( Checked ), new
PropertyMetadata( OnSetCommandCallback ) );
public static readonly DependencyProperty CheckedCommandBehaviorProperty =
DependencyProperty.RegisterAttached( "CheckedCommandBehavior", typeof( CheckedBehavior ), typeof( Checked ), null );
private static void OnSetCommandCallback( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e )
{
CheckBox element = dependencyObject as CheckBox;
if( element != null )
{
CheckedBehavior behavior = GetOrCreateBehavior( element );
behavior.Command = e.NewValue as ICommand;
}
}
private static CheckedBehavior GetOrCreateBehavior( CheckBox element )
{
CheckedBehavior behavior = element.GetValue( CheckedCommandBehaviorProperty ) as CheckedBehavior;
if( behavior == null )
{
behavior = new CheckedBehavior( element );
element.SetValue( CheckedCommandBehaviorProperty, behavior );
}
return behavior;
}
public static CheckedBehavior GetCheckCommandBehavior( DependencyObject obj )
{
return (CheckedBehavior) obj.GetValue( CheckedCommandBehaviorProperty );
}
public static void SetCheckCommandBehavior( DependencyObject obj, CheckedBehavior value )
{
obj.SetValue( CheckedCommandBehaviorProperty, value );
}
}

Your sample is not enough for a repro on my PC, but here are the things that I'd correct first:
The bindings in the DataTemplate are missing ", Mode=TwoWay" if you want the Enabled property to be set in your PanelItem
(- The ItemsSource binding does not need the Mode=TwoWay, but this is a minor detail)
The DataContext of the ItemTemplate is the PanelItem instance, so the binding of the Check command seems wrong: there is no Check property on PanelItem. The binding should be:
my:Checked.Command="{Binding ElementName=MyListBox, Path=DataContext.Check}
This kind of stuff is always hard to debug. Look at the output window of VS; binding errors (path not found) are displayed there. When you have a DP change callback (like OnSetCommandCallback), a breakpoint there will tell you how the binding went.
Edit: added after 1st comment (as I can't use the comment feature on the PC I have to use now)
The Command attached property is defined as type CheckBox in the Checked class, but the Check property in the VM is a DelegateCommand. I agree with WPF on the type mismatch :-)
The property declaration is like this:
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command", typeof( CheckBox ),
typeof( Checked ), new PropertyMetadata( OnSetCommandCallback ) );
The second parameter should be the property type, so I guess something like ICommand in your case.
Out of curiosity: in OnSetCommandCallback, you don't care for the value set to the Command property (which is in e.NewValue). How do you relate an instance of CheckedBehavior to the Check property of the VM ?
Edit after second comment:
No, the 2nd paragraph above is not related to your question. Maybe it does not make sense. I can't figure out the role of CheckedBehavior.
Concerning the question of which item is checked/unchecked: what do you need more precisely ? You have a PanelItem instance, whose Enabled property is being set to true or false through the biding; so the checked items are the ones with Enabled=true.
Edit after 3rd comment:
Thanks for the explanation of your needs. You're not really using the path parameter of the binding to the attached property, you could write:
my:Checked.Command="{Binding}"
This way, e.NewValue is the bound PanelItem in the OnSetCommandCallback. So it could be given to the CheckedBehavior instance (in its constructor), which could forward it when calling Execute of ICommand.

CheckBehavior.cs:
public class CheckBehavior : CommandBehaviorBase<CheckBox>
{
public CheckBehavior(CheckBox element) : base(element)
{
element.Checked += OnElementChecked;
element.Unchecked += OnElementChecked;
}
private void OnElementChecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox && ((CheckBox)sender).IsPressed)
{
base.ExecuteCommand();
}
}
}
CheckCommand.cs:
public class CheckCommand
{
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
public static object GetCommandParameter(DependencyObject obj)
{
return obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CheckCommand), new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CheckCommand), new PropertyMetadata(OnSetCommandParameterCallback));
public static readonly DependencyProperty CheckedCommandBehaviorProperty =
DependencyProperty.RegisterAttached("CheckedCommandBehavior", typeof(CheckBehavior), typeof(CheckCommand), null);
private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
CheckBox element = dependencyObject as CheckBox;
if (element != null)
{
CheckBehavior behavior = GetOrCreateBehavior(element);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
CheckBox element = dependencyObject as CheckBox;
if (element != null)
{
CheckBehavior behavior = GetOrCreateBehavior(element);
behavior.CommandParameter = e.NewValue;
}
}
private static CheckBehavior GetOrCreateBehavior(CheckBox element)
{
CheckBehavior behavior = element.GetValue(CheckedCommandBehaviorProperty) as CheckBehavior;
if (behavior == null)
{
behavior = new CheckBehavior(element);
element.SetValue(CheckedCommandBehaviorProperty, behavior);
}
return behavior;
}
public static CheckBehavior GetCheckCommandBehavior(DependencyObject obj)
{
return (CheckBehavior)obj.GetValue(CheckedCommandBehaviorProperty);
}
public static void SetCheckCommandBehavior(DependencyObject obj, CheckBehavior value)
{
obj.SetValue(CheckedCommandBehaviorProperty, value);
}
}
And example:
<CheckBox Commands:CheckCommand.Command="{Binding MyCheckCommand}}"
Commands:CheckCommand.CommandParameter="{Binding}"/>

Related

xaml Convert ID to String without using ComboBox

I've been using code like this
<ComboBox ItemsSource="{Binding Path=CompaniesViewModel.CompaniesCollection}"
SelectedValuePath="CompanyId"
SelectedValue="{Binding Path=CompanyId}"
IsEnabled="False"
DisplayMemberPath="CompanyName"
/>
To display a Company Name in a ComboBox. Notice how the IsEnabled is set to false...that's because I really don't want the user to use the ComboBox. I am just using it as an easy way to convert an ID to string for display purposes.
When I put items in a Grid and there are a lot of them, I think it is really hurting the rendering performance. When I remove the ComboBox it loads in a split second. When the ComboBox is used in the code it can take 20 seconds.
I guess my question is I think I should be using a Label or TextBlock but not sure how to get the binding to work correctly as They don't have an ItemsSource or a SelectedValuePath or SelectedValue.
I thought about writing an IValueConverter but not sure how to bind/pass in the 3 values. I'd have to pass in the collection, the ValuePath and the Value ID.
Any thoughts or suggestions?
Put a
public Company Company {get {return CompaniesCollection.FirstOrDefault(x => x.CompanyId == CompanyId); }}
property in the ViewModel.
I welcome any of you to examine my code to see if you can make it more efficient but this is what I ended up doing.
<cc:LookupLabel
ItemsSource="{Binding Path=CompaniesCollection}"
SelectedValuePath="CompanyId"
SelectedValue="{Binding Path=CompanyId}"
DisplayMemberPath="CompanyName"
/>
And below is the LookupLabel derived from Label and INotifyPropertyChanged. I'm not sure how Microsoft implements this efficiently but this was my best stab at it. In particular the GetContent method listed at the bottom. All the other stuff is just the messy DependencyProperty declarations.
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public class LookupLabel : Label, INotifyPropertyChanged
{
public LookupLabel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region ItemsSource
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(LookupLabel)
, new UIPropertyMetadata(null, LookupLabel.ItemsSourceChanged)
);
private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("ItemsSource");
t.Content = GetContent(t);
}
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
#endregion ItemsSource
#region SelectedValue
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(LookupLabel)
, new UIPropertyMetadata(null, LookupLabel.SelectedValueChanged)
);
private static void SelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("SelectedValue");
t.Content = GetContent(t);
}
[Localizability(LocalizationCategory.NeverLocalize)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Bindable(true)]
[Category("Appearance")]
public object SelectedValue
{
get
{
return (object)GetValue(SelectedValueProperty);
}
set
{
SetValue(SelectedValueProperty, value);
}
}
#endregion SelectedValue
#region SelectedValuePath
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(LookupLabel)
, new UIPropertyMetadata(string.Empty, LookupLabel.SelectedValuePathChanged)
);
private static void SelectedValuePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("SelectedValuePath");
t.Content = GetContent(t);
}
[Localizability(LocalizationCategory.NeverLocalize)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Bindable(true)]
[Category("Appearance")]
public string SelectedValuePath
{
get
{
return (string)GetValue(SelectedValuePathProperty);
}
set
{
SetValue(SelectedValuePathProperty, value);
}
}
#endregion SelectedValuePath
#region DisplayMemberPath
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(LookupLabel)
, new UIPropertyMetadata(string.Empty, LookupLabel.DisplayMemberPathChanged)
);
private static void DisplayMemberPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("DisplayMemberPath");
t.Content = GetContent(t);
}
[Localizability(LocalizationCategory.NeverLocalize)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Bindable(true)]
[Category("Appearance")]
public string DisplayMemberPath
{
get
{
return (string)GetValue(DisplayMemberPathProperty);
}
set
{
SetValue(DisplayMemberPathProperty, value);
}
}
#endregion DisplayMemberPath
protected static object GetContent(LookupLabel label)
{
if (label.ItemsSource == null)
{
return null;
}
if (string.IsNullOrWhiteSpace(label.SelectedValuePath))
{
return null;
}
if (string.IsNullOrWhiteSpace(label.DisplayMemberPath))
{
return null;
}
if (label.SelectedValue == null)
{
return null;
}
object result = null;
System.Reflection.PropertyInfo valuePropertyInfo = null;
foreach (var item in label.ItemsSource)
{
if (valuePropertyInfo == null)
{
valuePropertyInfo = item.GetType().GetProperty(label.SelectedValuePath);
if (valuePropertyInfo == null)
{
return null;
}
}
if (valuePropertyInfo.GetValue(item, null).Equals(label.SelectedValue))
{
var displayPropertInfo = item.GetType().GetProperty(label.DisplayMemberPath);
if (displayPropertInfo == null)
{
return null;
}
else
{
result = displayPropertInfo.GetValue(item, null);
break;
}
}
}
return result;
}
}
}
I am guessing that your combobox is taking too long to load because your collection has a lot of items.
You shouldn't be loading all your companies if you'll just show one of them, as a general good-practice.
I don't quite grasp your intent in using the combobox. Is it the style? can it be enabled in the future?
If it is only an easy way of displaying the CompanyName then i'd suggest the following:
Bind directly to CompanyName property.
-OR-
in the code-behind, create a calculated property named "CompanyDisplayName" that gets your company name.
Bind to it in the XAML
in the code-behind, whenever the current selected Company instance or the CompanyId changes fire 'OnPropertyChanged("CompanyDisplayName")
Try a TextBlock or a readonly TextBox to enable copy/paste;
For more info on the NotifyPropertyCahnged paradigm read here

How do I bind an `DependenyProperty` to another attachable `DependencyProperty`?

I want to have a singleton that holds some values S1 and S2 that I can bind to. The goal is to have some UIElements update when its value changes. The problem is that I want to use the value inside of a reused DataTemplate. That means that I cannot bind directly to a dependency property of the singleton but this has to be set outside.
To correctly pass updates the values have to be DependencyProperty. Because I dont know to which property I have to bind I created another attachable property AttProperty of the same type as the values. Now I tried to bind the S1 to AttProperty but this gives me an error:
Additional information: A 'Binding' cannot be set on the
'SetAttProperty' property of type 'TextBox'. A 'Binding' can only be
set on a DependencyProperty of a DependencyObject.
So how can I bind with an attachable DependencyProperty to another DependencyProperty?
Here is the code for the singleton I have so far (C#):
public class DO : DependencyObject
{
// Singleton pattern (Expose a single shared instance, prevent creating additional instances)
public static readonly DO Instance = new DO();
private DO() { }
public static readonly DependencyProperty S1Property = DependencyProperty.Register(
"S1", typeof(string), typeof(DO),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public string S1
{
get { return (string)GetValue(S1Property); }
set { SetValue(S1Property, value); }
}
public static readonly DependencyProperty AttProperty = DependencyProperty.RegisterAttached(
"Att", typeof(string), typeof(DO),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender) );
public static void SetAttProperty(DependencyObject depObj, string value)
{
depObj.SetValue(AttProperty, value);
}
public static string GetAttProperty(DependencyObject depObj)
{
return (string)depObj.GetValue(AttProperty);
}
}
Here is the problematic thing (XAML):
<TextBox Name="Input" Text="" TextChanged="Input_TextChanged" local:DO.AttProperty="{Binding Source={x:Static local:DO.Instance}, Path=S1}" />
Update
With the changes of Bojin Li the errors go away. But one issue remains - if I now try to update the singleton with the help of the attached property like this:
<TextBox local:DO.Att="{Binding Source={x:Static local:DO.Instance}, Path=S1, Mode=TwoWay}" Text="{Binding Path=(local:DO.Att), RelativeSource={RelativeSource Self}, Mode=TwoWay}"/>
Why is the value not propagated to S1 in the singleton?
You need to implement INotifyPropertyChanged and wire the changes up to the dependency property changing.
public class DO : DependencyObject,INotifyPropertyChanged {
// Singleton pattern (Expose a single shared instance, prevent creating additional instances)
public static readonly DO Instance = new DO();
private DO() { }
public static readonly DependencyProperty S1Property = DependencyProperty.Register(
"S1", typeof(string), typeof(DO),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender,onS1Changed));
private static void onS1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
DO item = d as DO;
if (item != null) item.OnPropertyChanged(new PropertyChangedEventArgs("S1"));
}
public string S1 {
get { return (string)GetValue(S1Property); }
set { SetValue(S1Property, value); }
}
public static readonly DependencyProperty AttProperty = DependencyProperty.RegisterAttached(
"Att", typeof(string), typeof(DO),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender,onAttChanged));
private static void onAttChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
DO item = d as DO;
if (item != null) item.OnPropertyChanged(new PropertyChangedEventArgs("Att"));
}
public static void SetAttProperty(DependencyObject depObj, string value) {
depObj.SetValue(AttProperty, value);
}
public static string GetAttProperty(DependencyObject depObj) {
return (string)depObj.GetValue(AttProperty);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
I don't think you named your Get/Set Accessors correctly for your attached Property as documented here. Try this instead:
public static readonly DependencyProperty AttProperty = DependencyProperty.RegisterAttached(
"Att", typeof(string), typeof(DO),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public static void SetAtt(DependencyObject depObj, string value)
{
depObj.SetValue(AttProperty, value);
}
public static string GetAtt(DependencyObject depObj)
{
return (string)depObj.GetValue(AttProperty);
}
Example Binding:
<TextBlock local:DO.Att="{Binding Source={x:Static local:DO.Instance}, Path=S1}" Text="{Binding Path=(local:DO.Att),RelativeSource={RelativeSource Self}}">

Binding within an AttachedProperty of Type Collection to an other Element

I want to create an AttachedProperty of Type Collection, which contains references to other existing elements, as shown below:
<Window x:Class="myNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:myNamespace"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter>
<ContentPresenter.Content>
<Button>
<local:DependencyObjectCollectionHost.Objects>
<local:DependencyObjectCollection>
<local:DependencyObjectContainer Object="{Binding ElementName=myButton}"/>
</local:DependencyObjectCollection>
</local:DependencyObjectCollectionHost.Objects>
</Button>
</ContentPresenter.Content>
</ContentPresenter>
<Button x:Name="myButton" Grid.Row="1"/>
</Grid>
</Window>
Therefore I've created a generic class, called ObjectContainer, to gain the possibility to do so with Binding:
public class ObjectContainer<T> : DependencyObject
where T : DependencyObject
{
static ObjectContainer()
{
ObjectProperty = DependencyProperty.Register
(
"Object",
typeof(T),
typeof(ObjectContainer<T>),
new PropertyMetadata(null)
);
}
public static DependencyProperty ObjectProperty;
[Bindable(true)]
public T Object
{
get { return (T)this.GetValue(ObjectProperty); }
set { this.SetValue(ObjectProperty, value); }
}
}
public class DependencyObjectContainer : ObjectContainer<DependencyObject> { }
public class DependencyObjectCollection : Collection<DependencyObjectContainer> { }
public static class DependencyObjectCollectionHost
{
static DependencyObjectCollectionHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(DependencyObjectCollectionHost),
new PropertyMetadata(null, OnObjectsChanged)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static readonly DependencyProperty ObjectsProperty;
private static void OnObjectsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var objects = (DependencyObjectCollection)e.NewValue;
if (objects.Count != objects.Count(d => d.Object != null))
throw new ArgumentException();
}
}
I'm not able to establish any binding within the Collection. I think I've already figured out, what the problem is. The elements in the Collection have no DataContext related to the Binding. However, I've no clue what I can do against it.
EDIT:
Fixed the missing Name Property of the Button.
Note: I know that the binding cannot work, because every Binding which doesn't declare a Source explicitly will use it's DataContext as it's Source. Like I already mentioned: We don't have such a DataContext within my Collection and there's no VisualTree where the non-existing FrameworkElement could be part of ;)
Maybe someone had a similiar problem in the past and found a suitable solution.
EDIT2 related to H.B.s post:
With the following change to the items within the collection it seems to work now:
<local:DependencyObjectContainer Object="{x:Reference myButton}"/>
Interesting behavior:
When the OnObjectsChanged Event-Handler is called, the collection contains zero elements ... I assume that's because the creation of the elements (done within the InitializeComponent method) hasn't finished yet.
Btw. As you H.B. said the use of the Container class is unnecessary when using x:Reference. Are there any disadvantages when using x:Reference which I don't see at the first moment?
EDIT3 Solution:
I've added a custom Attached Event in order to be notified, when the Collection changed.
public class DependencyObjectCollection : ObservableCollection<DependencyObject> { }
public static class ObjectHost
{
static KeyboardObjectHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(KeyboardObjectHost),
new PropertyMetadata(null, OnObjectsPropertyChanged)
);
ObjectsChangedEvent = EventManager.RegisterRoutedEvent
(
"ObjectsChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(KeyboardObjectHost)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static void AddObjectsChangedHandler(DependencyObject dependencyObject, RoutedEventHandler h)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.AddHandler(ObjectsChangedEvent, h);
else
throw new ArgumentException(string.Format("Cannot add handler to object of type: {0}", dependencyObject.GetType()), "dependencyObject");
}
public static void RemoveObjectsChangedHandler(DependencyObject dependencyObject, RoutedEventHandler h)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.RemoveHandler(ObjectsChangedEvent, h);
else
throw new ArgumentException(string.Format("Cannot remove handler from object of type: {0}", dependencyObject.GetType()), "dependencyObject");
}
public static bool CanControlledByKeyboard(DependencyObject dependencyObject)
{
var objects = GetObjects(dependencyObject);
return objects != null && objects.Count != 0;
}
public static readonly DependencyProperty ObjectsProperty;
public static readonly RoutedEvent ObjectsChangedEvent;
private static void OnObjectsPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
Observable.FromEvent<NotifyCollectionChangedEventArgs>(e.NewValue, "CollectionChanged")
.DistinctUntilChanged()
.Subscribe(args =>
{
var objects = (DependencyObjectCollection)args.Sender;
if (objects.Count == objects.Count(d => d != null)
OnObjectsChanged(dependencyObject);
else
throw new ArgumentException();
});
}
private static void OnObjectsChanged(DependencyObject dependencyObject)
{
RaiseObjectsChanged(dependencyObject);
}
private static void RaiseObjectsChanged(DependencyObject dependencyObject)
{
var uiElement = dependencyObject as UIElement;
if (uiElement != null)
uiElement.RaiseEvent(new RoutedEventArgs(ObjectsChangedEvent));
}
}
You can use x:Reference in .NET 4, it's "smarter" than ElementName and unlike bindings it does not require the target to be a dependency property.
You can even get rid of the container class, but your property needs to have the right type so the ArrayList can directly convert to the property value instead of adding the whole list as an item. Using x:References directly will not work.
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
<local:AttachedProperties.Objects>
<col:ArrayList>
<x:Reference>button1</x:Reference>
<x:Reference>button2</x:Reference>
</col:ArrayList>
</local:AttachedProperties.Objects>
public static readonly DependencyProperty ObjectsProperty =
DependencyProperty.RegisterAttached
(
"Objects",
typeof(IList),
typeof(FrameworkElement),
new UIPropertyMetadata(null)
);
public static IList GetObjects(DependencyObject obj)
{
return (IList)obj.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject obj, IList value)
{
obj.SetValue(ObjectsProperty, value);
}
Further writing the x:References as
<x:Reference Name="button1"/>
<x:Reference Name="button2"/>
will cause some more nice errors.
I think the answer can be found in the following two links:
Binding.ElementName Property
XAML Namescopes and Name-related APIs
Especially the second states:
FrameworkElement has FindName, RegisterName and UnregisterName methods. If the object you call these methods on owns a XAML namescope, the methods call into the methods of the relevant XAML namescope. Otherwise, the parent element is checked to see if it owns a XAML namescope, and this process continues recursively until a XAML namescope is found (because of the XAML processor behavior, there is guaranteed to be a XAML namescope at the root). FrameworkContentElement has analogous behaviors, with the exception that no FrameworkContentElement will ever own a XAML namescope. The methods exist on FrameworkContentElement so that the calls can be forwarded eventually to a FrameworkElement parent element.
So the issue in your sample caused by the fact that your classes are DependencyObjects at most but none of them is FrameworkElement. Not being a FrameworkElement it cannot provide Parent property to resolve name specified in Binding.ElementName.
But this isn't end. In order to resolve names from Binding.ElementName your container not only should be a FrameworkElement but it should also have FrameworkElement.Parent. Populating attached property doesn't set this property, your instance should be a logical child of your button so it will be able to resolve the name.
So I had to make some changes into your code in order to make it working (resolving ElementName), but at this state I do not think it meets your needs. I'm pasting the code below so you can play with it.
public class ObjectContainer<T> : FrameworkElement
where T : DependencyObject
{
static ObjectContainer()
{
ObjectProperty = DependencyProperty.Register("Object", typeof(T), typeof(ObjectContainer<T>), null);
}
public static DependencyProperty ObjectProperty;
[Bindable(true)]
public T Object
{
get { return (T)this.GetValue(ObjectProperty); }
set { this.SetValue(ObjectProperty, value); }
}
}
public class DependencyObjectContainer : ObjectContainer<DependencyObject> { }
public class DependencyObjectCollection : FrameworkElement
{
private object _child;
public Object Child
{
get { return _child; }
set
{
_child = value;
AddLogicalChild(_child);
}
}
}
public static class DependencyObjectCollectionHost
{
static DependencyObjectCollectionHost()
{
ObjectsProperty = DependencyProperty.RegisterAttached
(
"Objects",
typeof(DependencyObjectCollection),
typeof(DependencyObjectCollectionHost),
new PropertyMetadata(null, OnObjectsChanged)
);
}
public static DependencyObjectCollection GetObjects(DependencyObject dependencyObject)
{
return (DependencyObjectCollection)dependencyObject.GetValue(ObjectsProperty);
}
public static void SetObjects(DependencyObject dependencyObject, DependencyObjectCollection value)
{
dependencyObject.SetValue(ObjectsProperty, value);
}
public static readonly DependencyProperty ObjectsProperty;
private static void OnObjectsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
((Button) dependencyObject).Content = e.NewValue;
var objects = (DependencyObjectCollection)e.NewValue;
// this check doesn't work anyway. d.Object was populating later than this check was performed
// if (objects.Count != objects.Count(d => d.Object != null))
// throw new ArgumentException();
}
}
Probably you still can make this working by implementing INameScope interface and its FindName method particularly but I haven't tried doing that.

Logically combine dependency properties

I'm using C# 4.0 and have created a DependencyObject MyView.
In MyView, I have two DependencyProperties, PropA and PropB, both are booleans.
I want a third DependencyProperty, PropC, also a bool, and simply put, should always give me (PropA || PropB).
What is the best way to accomplish this?
I was also thinking of making PropC a readonly DependencyProperty, but have read about issues with binding to readonly dp's (WPF ReadOnly Dependency Properties using MVVM)
You can use the Dependency Property changed callback for PropA and PropB to set the value for PropC (don't use the CLR property wrapper for the Dependency Properties as they are never guaranteed to be called).
If you have these three DP's
public static readonly DependencyProperty PropAProperty =
DependencyProperty.Register("PropA",
typeof(bool),
typeof(MyView),
new PropertyMetadata(false, PropAPropertyChanged));
public static readonly DependencyProperty PropBProperty =
DependencyProperty.Register("PropB",
typeof(bool),
typeof(MyView),
new PropertyMetadata(false, PropBPropertyChanged));
public static readonly DependencyProperty PropCProperty =
DependencyProperty.Register("PropC",
typeof(bool),
typeof(MyView),
new PropertyMetadata(false));
public bool PropA
{
get { return (bool)this.GetValue(PropAProperty); }
set { this.SetValue(PropAProperty, value); }
}
public bool PropB
{
get { return (bool)this.GetValue(PropBProperty); }
set { this.SetValue(PropBProperty, value); }
}
public bool PropC
{
get { return (bool)this.GetValue(PropCProperty); }
set { this.SetValue(PropCProperty, value); }
}
you can use the property changed callback like this
private static void PropAPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MyView myView = source as MyView;
myView.OnPropChanged();
}
private static void PropBPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MyView myView = source as MyView;
myView.OnPropChanged();
}
public void OnPropChanged()
{
PropC = PropA || PropB;
}
This way, you'll always update the value of PropC everytime PropA or PropB changes
Also, PropC doesn't need to be a DP, it can be a normal CLR property if you implement INotifyPropertyChanged. Then the implementation can look like this instead
public void OnPropChanged()
{
OnPropertyChanged("PropC");
}
public bool PropC
{
get
{
return PropA || PropB;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You could also bind PropC to PropA and PropB with a MultiBinding. Let me know if you want an example of this as well
The linked web page is for an unusual situation, a "push" binding. That is, a one-way-to-source binding was attempted on the read-only property, not on another property trying to bind to it. By contrast, if you want your property to be bindable to other properties using a one-way binding expression on the other property, then you can use a read-only dependency property without any issues.
Edit:
Here is an example:
<Grid>
<Grid.Resources>
<local:MyObject x:Key="myObject" PropertyA="True" PropertyB="False"/>
</Grid.Resources>
<StackPanel DataContext="{StaticResource myObject}">
<CheckBox IsChecked="{Binding PropertyA}" Content="PropertyA"/>
<CheckBox IsChecked="{Binding PropertyB}" Content="PropertyB"/>
<CheckBox IsChecked="{Binding PropertyC, Mode=OneWay}" IsEnabled="False" Content="PropertyC"/>
</StackPanel>
</Grid>
and the dependency properties, one of which is read-only:
public class MyObject : DependencyObject
{
public bool PropertyA
{
get { return (bool)GetValue(PropertyAProperty); }
set { SetValue(PropertyAProperty, value); }
}
public static readonly DependencyProperty PropertyAProperty =
DependencyProperty.Register("PropertyA", typeof(bool), typeof(MyObject), new UIPropertyMetadata(false, OnPropertyAOrBChanged));
public bool PropertyB
{
get { return (bool)GetValue(PropertyBProperty); }
set { SetValue(PropertyBProperty, value); }
}
public static readonly DependencyProperty PropertyBProperty =
DependencyProperty.Register("PropertyB", typeof(bool), typeof(MyObject), new UIPropertyMetadata(false, OnPropertyAOrBChanged));
public bool PropertyC
{
get { return (bool)GetValue(PropertyCProperty); }
set { SetValue(PropertyCPropertyKey, value); }
}
private static readonly DependencyPropertyKey PropertyCPropertyKey =
DependencyProperty.RegisterReadOnly("PropertyC", typeof(bool), typeof(MyObject), new UIPropertyMetadata());
public static readonly DependencyProperty PropertyCProperty = PropertyCPropertyKey.DependencyProperty;
private static void OnPropertyAOrBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObject = d as MyObject;
myObject.PropertyC = myObject.PropertyA || myObject.PropertyB;
}
}

OneWayToSource binding from readonly property in XAML

I'm trying to bind to a Readonly property with OneWayToSource as mode, but it seems this cannot be done in XAML:
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWayToSource}" />
I get:
The property 'FlagThingy.IsModified' cannot be set because it does not have an accessible set accessor.
IsModified is a readonly DependencyProperty on FlagThingy. I want to bind that value to the FlagIsModified property on the container.
To be clear:
FlagThingy.IsModified --> container.FlagIsModified
------ READONLY ----- ----- READWRITE --------
Is this possible using just XAML?
Update: Well, I fixed this case by setting the binding on the container and not on the FlagThingy. But I'd still like to know if this is possible.
Some research results for OneWayToSource...
Option # 1.
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Binding binding = new Binding();
binding.Path = new PropertyPath("FlagIsModified");
binding.ElementName = "container";
binding.Mode = BindingMode.OneWayToSource;
_flagThingy.SetBinding(FlagThingy.IsModifiedProperty, binding);
Option # 2
// Control definition
public partial class FlagThingy : UserControl
{
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
}
<controls:FlagThingy IsModified="{Binding Path=FlagIsModified,
ElementName=container, Mode=OneWayToSource}" />
Option # 3 (True read-only dependency property)
System.ArgumentException: 'IsModified' property cannot be data-bound.
// Control definition
public partial class FlagThingy : UserControl
{
private static readonly DependencyPropertyKey IsModifiedKey =
DependencyProperty.RegisterReadOnly("IsModified", typeof(bool), typeof(FlagThingy), new PropertyMetadata());
public static readonly DependencyProperty IsModifiedProperty =
IsModifiedKey.DependencyProperty;
}
<controls:FlagThingy x:Name="_flagThingy" />
// Binding Code
Same binding code...
Reflector gives the answer:
internal static BindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, Binding binding, BindingExpressionBase parent)
{
FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata;
if (((fwMetaData != null) && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly)
{
throw new ArgumentException(System.Windows.SR.Get(System.Windows.SRID.PropertyNotBindable, new object[] { dp.Name }), "dp");
}
....
This is a limitation of WPF and it is by design. It is reported on Connect here:
OneWayToSource binding from a readonly dependency property
I made a solution to dynamically be able to push read-only dependency properties to the source called PushBinding which I blogged about here. The example below does OneWayToSource Bindings from the read-only DP's ActualWidth and ActualHeight to the Width and Height properties of the DataContext
<TextBlock Name="myTextBlock">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
PushBinding works by using two Dependency Properties, Listener and Mirror. Listener is bound OneWay to the TargetProperty and in the PropertyChangedCallback it updates the Mirror property which is bound OneWayToSource to whatever was specified in the Binding.
Demo Project can be Downloaded Here.
It contains source code and short sample usage.
Wrote this:
Usage:
<TextBox Text="{Binding Text}"
p:OneWayToSource.Bind="{p:Paths From={x:Static Validation.HasErrorProperty},
To=SomeDataContextProperty}" />
Code:
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
public static class OneWayToSource
{
public static readonly DependencyProperty BindProperty = DependencyProperty.RegisterAttached(
"Bind",
typeof(ProxyBinding),
typeof(OneWayToSource),
new PropertyMetadata(default(Paths), OnBindChanged));
public static void SetBind(this UIElement element, ProxyBinding value)
{
element.SetValue(BindProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static ProxyBinding GetBind(this UIElement element)
{
return (ProxyBinding)element.GetValue(BindProperty);
}
private static void OnBindChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ProxyBinding)e.OldValue)?.Dispose();
}
public class ProxyBinding : DependencyObject, IDisposable
{
private static readonly DependencyProperty SourceProxyProperty = DependencyProperty.Register(
"SourceProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object), OnSourceProxyChanged));
private static readonly DependencyProperty TargetProxyProperty = DependencyProperty.Register(
"TargetProxy",
typeof(object),
typeof(ProxyBinding),
new PropertyMetadata(default(object)));
public ProxyBinding(DependencyObject source, DependencyProperty sourceProperty, string targetProperty)
{
var sourceBinding = new Binding
{
Path = new PropertyPath(sourceProperty),
Source = source,
Mode = BindingMode.OneWay,
};
BindingOperations.SetBinding(this, SourceProxyProperty, sourceBinding);
var targetBinding = new Binding()
{
Path = new PropertyPath($"{nameof(FrameworkElement.DataContext)}.{targetProperty}"),
Mode = BindingMode.OneWayToSource,
Source = source
};
BindingOperations.SetBinding(this, TargetProxyProperty, targetBinding);
}
public void Dispose()
{
BindingOperations.ClearAllBindings(this);
}
private static void OnSourceProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(TargetProxyProperty, e.NewValue);
}
}
}
[MarkupExtensionReturnType(typeof(OneWayToSource.ProxyBinding))]
public class Paths : MarkupExtension
{
public DependencyProperty From { get; set; }
public string To { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetObject = (UIElement)provideValueTarget.TargetObject;
return new OneWayToSource.ProxyBinding(targetObject, this.From, this.To);
}
}
Have not tested it in styles and templates yet, guess it needs special casing.
Here is another implementation for binding to Validation.HasError
public static class OneWayToSource
{
public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached(
"Bindings",
typeof(OneWayToSourceBindings),
typeof(OneWayToSource),
new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged));
public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value)
{
element.SetValue(BindingsProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static OneWayToSourceBindings GetBindings(this FrameworkElement element)
{
return (OneWayToSourceBindings)element.GetValue(BindingsProperty);
}
private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty);
((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d);
}
}
public class OneWayToSourceBindings : FrameworkElement
{
private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext));
private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})");
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
nameof(HasError),
typeof(bool),
typeof(OneWayToSourceBindings),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
"Element",
typeof(UIElement),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(UIElement), OnElementChanged));
private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached(
"HasErrorProxy",
typeof(bool),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(bool), OnHasErrorProxyChanged));
public bool HasError
{
get { return (bool)this.GetValue(HasErrorProperty); }
set { this.SetValue(HasErrorProperty, value); }
}
private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(HasErrorProperty, e.NewValue);
}
private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
BindingOperations.ClearBinding(d, DataContextProperty);
BindingOperations.ClearBinding(d, HasErrorProxyProperty);
}
else
{
var dataContextBinding = new Binding
{
Path = DataContextPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding);
var hasErrorBinding = new Binding
{
Path = HasErrorPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding);
}
}
}
Usage in xaml
<StackPanel>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
<local:OneWayToSource.Bindings>
<local:OneWayToSourceBindings HasError="{Binding HasError}" />
</local:OneWayToSource.Bindings>
</TextBox>
<CheckBox IsChecked="{Binding HasError, Mode=OneWay}" />
</StackPanel>
This implementation is specific to binding Validation.HasError
Here's another attached property solution based on SizeObserver detailed here Pushing read-only GUI properties back into ViewModel
public static class MouseObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(MouseObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedMouseOverProperty = DependencyProperty.RegisterAttached(
"ObservedMouseOver",
typeof(bool),
typeof(MouseObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.SetValue(ObserveProperty, observe);
}
public static bool GetObservedMouseOver(FrameworkElement frameworkElement)
{
return (bool)frameworkElement.GetValue(ObservedMouseOverProperty);
}
public static void SetObservedMouseOver(FrameworkElement frameworkElement, bool observedMouseOver)
{
frameworkElement.SetValue(ObservedMouseOverProperty, observedMouseOver);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.MouseEnter += OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave += OnFrameworkElementMouseOverChanged;
UpdateObservedMouseOverForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.MouseEnter -= OnFrameworkElementMouseOverChanged;
frameworkElement.MouseLeave -= OnFrameworkElementMouseOverChanged;
}
}
private static void OnFrameworkElementMouseOverChanged(object sender, MouseEventArgs e)
{
UpdateObservedMouseOverForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedMouseOverForFrameworkElement(FrameworkElement frameworkElement)
{
frameworkElement.SetCurrentValue(ObservedMouseOverProperty, frameworkElement.IsMouseOver);
}
}
Declare attached property in control
<ListView ItemsSource="{Binding SomeGridItems}"
ut:MouseObserver.Observe="True"
ut:MouseObserver.ObservedMouseOver="{Binding IsMouseOverGrid, Mode=OneWayToSource}">
WPF will not use the CLR property setter, but seems it does some odd validation based on it.
May be in your situation this can be ok:
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { throw new Exception("An attempt ot modify Read-Only property"); }
}
Hmmm... I'm not sure I agree with any of these solutions. How about specifying a coercion callback in your property registration that ignores external change? For instance, I needed to implement a read-only Position dependency property to get the position of a MediaElement control inside a user control. Here's how I did it:
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(double), typeof(MediaViewer),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnPositionChanged, OnPositionCoerce));
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as MediaViewer;
}
private static object OnPositionCoerce(DependencyObject d, object value)
{
var ctrl = d as MediaViewer;
var position = ctrl.MediaRenderer.Position.TotalSeconds;
if (ctrl.MediaRenderer.NaturalDuration.HasTimeSpan == false)
return 0d;
else
return Math.Min(position, ctrl.Duration);
}
public double Position
{
get { return (double)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
In other words, simply ignore the change and return the value backed by a different member that does not have a public modifier. -- In the above example, MediaRenderer is actually the private MediaElement control.
The way I worked around this limitation was to expose only a Binding property in my class, keeping the DependencyProperty private altogether. I implemented a "PropertyBindingToSource" write-only property (this one not a DependencyProperty) which can be set to a binding value in the xaml. In the setter for this write-only property I call to BindingOperations.SetBinding to link the binding to the DependencyProperty.
For the OP's specific example, it would look like this:
The FlatThingy implementation:
public partial class FlatThingy : UserControl
{
public FlatThingy()
{
InitializeComponent();
}
public Binding IsModifiedBindingToSource
{
set
{
if (value?.Mode != BindingMode.OneWayToSource)
{
throw new InvalidOperationException("IsModifiedBindingToSource must be set to a OneWayToSource binding");
}
BindingOperations.SetBinding(this, IsModifiedProperty, value);
}
}
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
private set { SetValue(IsModifiedProperty, value); }
}
private static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(FlatThingy), new PropertyMetadata(false));
private void Button_Click(object sender, RoutedEventArgs e)
{
IsModified = !IsModified;
}
}
Notice that the static readonly DependencyProperty object is private. In the control I added a button whose click is handled by Button_Click.
The use of the FlatThingy control in my window.xaml:
<Window x:Class="ReadOnlyBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ReadOnlyBinding"
mc:Ignorable="d"
DataContext="{x:Static local:ViewModel.Instance}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding FlagIsModified}" Grid.Row="0" />
<local:FlatThingy IsModifiedBindingToSource="{Binding FlagIsModified, Mode=OneWayToSource}" Grid.Row="1" />
</Grid>
Note that I've also implemented a ViewModel for binding to that is not shown here. It exposes a DependencyProperty named "FlagIsModified" as you can glean from the source above.
It works great, allowing me to push information back into the ViewModel from the View in a loosely coupled manner, with the direction of that information flow explicitly defined.
You're doing the binding in the wrong direction right now. OneWayToSource will try and update FlagIsModified on container whenever IsModified changes on the control you are creating. You want the opposite, which is to have IsModified bind to container.FlagIsModified. For that you should use the binding mode OneWay
<controls:FlagThingy IsModified="{Binding FlagIsModified,
ElementName=container,
Mode=OneWay}" />
Full list of enumeration members: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx

Resources