I have a need for resolving the target DependencyObject in my MarkupExtension in order to wire up some things on the target. The code below works fine when the markup extension is used directly on a FrameworkElement in XAML, or when used on a FrameworkElement inside of a ControlTemplate, however it does not work when the markup extension is used in the Value property of a Setter on a Style. Can anyone point me in the right direction if it is even possible? I tried digging through the documentation (and also the reference source), but was unable to find exactly what I need.
My markup extension looks like this:
internal class CustomMarkupExtension : MarkupExtension
{
public object Resource { get; set; }
public CustomMarkupExtension(object resource)
{
Resource = resource;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (provideValueTarget.TargetObject.GetType().FullName == "System.Windows.SharedDp")
{
// In a control template the TargetObject is a SharedDp (internal WPF class)
// In that case, the markup extension itself is returned to be re-evaluated later in the context where the template is applied
return this;
}
DependencyObject targetDependencyObject = null;
var targetObject = provideValueTarget.TargetObject;
if (targetObject is DependencyObject dependencyObject)
{
// SUCCESS!! we now have a valid target dependency object
targetDependencyObject = dependencyObject;
}
else if (targetObject is Setter setter)
{
// Markup extension is used on a setter within a Style.
// TODO: How do I resolve the target dependency object in this case?
}
if (targetDependencyObject != null)
{
// ... Wire up whatever I need on the target dependency object ...
}
... details omitted ...
}
}
It works fine when used directly on a FrameworkElement either directly, or within a ControlTemplate:
<Grid Background="{local:CustomMarkup MyResource}" />
When used from a Style, I am unable to resolve the target dependency object - see the TODO in the code:
<Style x:Key="CustomGridStyle" TargetType="Grid">
<Setter Property="Background" Value="{local:CustomMarkup MyResource}" />
</Style>
Any help would be much appreciated. Thanks.
Related
Working Example with "Binding":
I have a UserControl which I use like this in my MainWindow:
<userControls:NoMarkupControl/>
The ViewModel of my MainWindow contains this property:
private string _exampleText = "example";
public string ExampleText
{
get { return _exampleText; }
set
{
_exampleText = value;
OnPropertyChanged();
}
}
inside the UserControl I bind my ViewModel to this property:
<TextBlock Text="{Binding ExampleText}"/>
as a result "example" gets displayed when I start the app. Everything works.
Not working example with Custom Markup Extension:
Now I have a MarkupExtension:
public class ExampleTextExtension : MarkupExtension
{
private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();
private readonly object _parameter;
private DependencyProperty _dependencyProperty;
public ExampleTextExtension(object parameter)
{
_parameter = parameter;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
if (target?.TargetObject is DependencyObject dependencyObject &&
target.TargetProperty is DependencyProperty)
{
targetObject = dependencyObject;
}
else
{
return this;
}
_dependencyProperty = SetUnusedStorageProperty(targetObject, _parameter);
return GetLocalizedText((string)targetObject.GetValue(_dependencyProperty));
}
private static string GetLocalizedText(string text)
{
return text == null ? null : $"markup: {text}";
}
private static DependencyProperty SetUnusedStorageProperty(DependencyObject obj, object value)
{
var property = StorageProperties.FirstOrDefault(p => obj.ReadLocalValue(p) == DependencyProperty.UnsetValue);
if (property == null)
{
property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
StorageProperties.Add(property);
}
if (value is MarkupExtension markupExtension)
{
var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property));
obj.SetValue(property, resolvedValue);
}
else
{
obj.SetValue(property, value);
}
return property;
}
private class ServiceProvider : IServiceProvider, IProvideValueTarget
{
public object TargetObject { get; }
public object TargetProperty { get; }
public ServiceProvider(object targetObject, object targetProperty)
{
TargetObject = targetObject;
TargetProperty = targetProperty;
}
public object GetService(Type serviceType)
{
return serviceType.IsInstanceOfType(this) ? this : null;
}
}
}
Again I have a UserControl which I use like this in my MainWindow:
<userControls:MarkupControl/>
The ViewModel of my MainWindow stays the same like above.
inside the UserControl I bind to my TextBlock Text property like this:
<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>
as a result my UserControl displays nothing. I would have expected to display "markup: example"
The binding somehow does not work in this case.
Does anybody know how to fix this?
Additional information:
it works when used like this (dependency property MarkupText is created in user control):
<userControls:MarkupControl MarkupText={markupExtensions:ExampleText {Binding ExampleText}}/>
<TextBlock Text="{Binding Text, ElementName=MarkupControl}"/>
Firstly, you need to refactor your extension to simplify the implementation. You don't need a static context here. Getting rid of the class context will make the tracking of the created attached properties obsolete. You can drop the related collection safely. In your case, it's more efficient to store values in an instance context. Attached properties are also a convenient solution to store values per instance especially in a static context.
Secondly, you got a timing issue. The first time the extension is called, the Binding is not initialized properly: it doesn't provide the final value of the Binding.Source.
Additionally, your current implementation does not support property changes.
To fix this, you would have to track the Binding.Target updates when a value is sent from the Binding.Source (for a default BindingMode.OneWay). You can achieve this by listening to the Binding.TargetUpdated event (as stated in my previous comment) or register a property changed handler with the attached property (recommended).
To support two way binding, you would also have to track the target property (the property your MarkupExtension is assigned to).
A fixed and improved version could look as follows:
public class ExampleTextExtension : MarkupExtension
{
private static DependencyProperty ResolvedBindingSourceValueProperty = DependencyProperty.RegisterAttached(
"ResolvedBindingSourceValue",
typeof(object),
typeof(ExampleTextExtension),
new PropertyMetadata(default(object), OnResolvedBindingSourceValueChanged));
// Use attached property to store the target object
// for reference from a static context without dealing with class level members that are shared between instances.
private static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
"TargetProperty",
typeof(DependencyProperty),
typeof(ExampleTextExtension),
new PropertyMetadata(default));
private Binding Binding { get; }
// Accept BindingBase to support MultiBinding etc.
public ExampleTextExtension(Binding binding)
{
this.Binding = binding;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTargetService = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provideValueTargetService?.TargetObject is not DependencyObject targetObject
|| provideValueTargetService?.TargetProperty is not DependencyProperty targetProperty)
{
return this;
}
targetObject.SetValue(ExampleTextExtension.TargetPropertyProperty, targetProperty);
AttachBinding(targetObject);
return string.Empty;
}
private static string GetLocalizedText(string text)
=> String.IsNullOrWhiteSpace(text)
? string.Empty
: $"markup: {text}";
// By now, only supports OneWay binding
private void AttachBinding(DependencyObject targetObject)
{
switch (this.Binding.Mode)
{
case BindingMode.OneWay:
case BindingMode.Default:
HandleOneWayBinding(targetObject); break;
default: throw new NotSupportedException();
}
}
private void HandleOneWayBinding(DependencyObject targetObject)
{
BindingOperations.SetBinding(targetObject, ExampleTextExtension.ResolvedBindingSourceValueProperty, this.Binding);
}
// Property changed handler to update the target of this extension
// with the localized value
private static void OnResolvedBindingSourceValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string localizedText = GetLocalizedText(e.NewValue as string);
var targetProperty = d.GetValue(ExampleTextExtension.TargetPropertyProperty) as DependencyProperty;
d.SetValue(targetProperty, localizedText);
}
}
Remarks
There are better solutions to introduce localization without compromising the general syntax or legacy code. For example, introducing this MarkupExtension to existing code will break this code as all relevant data bindings (C# and XAML) have to be modified.
The most common approach is to use satellite assemblies and localized resources. Instead of converting text values during data binding you should localize the value source directly (so that the Binding transfers already localized values).
In other words, make sure that the data source is localized. Let the binding source expose the text by fetching it from a localized repository.
Is it possible to set the value behind a two-way binding directly, without knowing the bound property?
I have an attached property that is bound to a property like this:
<Element my:Utils.MyProperty="{Binding Something}" />
Now I want to change the value that is effectively stored in Something from the perspective of the attached property. So I cannot access the bound property directly, but only have references to the DependencyObject (i.e. the Element instance) and the DependencyProperty object itself.
The problem when simply setting it via DependencyObject.SetValue is that this effectively removes the binding, but I want to change the underlying bound property.
Using BindingOperations I can get both the Binding and the BindingExpression. Now is there a way to access the property behind it and change its value?
Okay, I have solved this now myself using a few reflection tricks on the binding expression.
I basically look at the binding path and the responding data item and try to resolve the binding myself. This will probably fail for more complex binding paths but for a simple property name as in my example above, this should work fine.
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(dependencyObj, dependencyProperty);
if (bindingExpression != null)
{
PropertyInfo property = bindingExpression.DataItem.GetType().GetProperty(bindingExpression.ParentBinding.Path.Path);
if (property != null)
property.SetValue(bindingExpression.DataItem, newValue, null);
}
Try setting a default value in the PropertyMetadata
You can find more information on MSDN -
http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.aspx
Here is an example :
public Boolean State
{
get { return (Boolean)this.GetValue(StateProperty); }
set { this.SetValue(StateProperty, value); }
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(
"State", typeof(Boolean), typeof(MyStateControl),new PropertyMetadata(myDefaultValue));
The problem when simply setting it via DependencyObject.SetValue is
that this effectively removes the binding, but I want to change the
underlying bound property.
This is true if the Binding.Mode is set to OneWay. If it is set to TwoWay, using DependencyObject.SetValue won't remove its binding.
This is a quote from Pro WPF 4.5 in C# (page 232):
Removing a binding: If you want to remove a binding so that you can
set a property in the usual way, you need the help of the
ClearBinding() or ClearAllBindings() method. It isn’t enough to simply
apply a new value to the property. If you’re using a two-way binding,
the value you set is propagated to the linked object, and both
properties remain synchronized.
So, to be able to change (and propagate) the my:Utils.MyProperty with SetValue without removing its binding:
<Element my:Utils.MyProperty="{Binding Something, Mode=TwoWay}" />
You can pass value via a binding on a dummy object.
public static void SetValue(BindingExpression exp, object value)
{
if (exp == null)
throw new ValueCannotBeNullException(() => exp);
Binding dummyBinding = new Binding(exp.ParentBinding.Path.Path);
dummyBinding.Mode = BindingMode.OneWayToSource;
dummyBinding.Source = exp.DataItem;
SetValue(dummyBinding, value);
}
public static void SetValue(Binding binding, object value)
{
BindingDummyObject o = new BindingDummyObject();
BindingOperations.SetBinding(o, BindingDummyObject.ValueProperty, binding);
o.Value = value;
BindingOperations.ClearBinding(o, BindingDummyObject.ValueProperty);
}
This is my dummy object
internal class BindingDummyObject : DependencyObject
{
public object Value
{
get
{
return (object)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(BindingDummyObject));
}
I had a similar issue when trying to implement a menu that was backed by an enumeration. I wanted to be able to set the underlying property (which was an enum) to the value associated with the menu item.
In my example, I attached two properties to an MenuItem:
public static readonly DependencyProperty EnumTargetProperty = DependencyProperty.RegisterAttached(
"EnumTarget",
typeof(object),
typeof(MenuItem),
new PropertyMetadata(null, EnumTargetChangedCallback)
);
public static readonly DependencyProperty EnumValueProperty = DependencyProperty.RegisterAttached(
"EnumValue",
typeof(object),
typeof(MenuItem),
new PropertyMetadata(null, EnumValueChangedCallback)
);
And the markup looks like this:
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="local:EnumMenuItem.EnumValue" Value="{Binding EnumMember}"/>
<Setter Property="local:EnumMenuItem.EnumTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=DataContext.Settings.AutoUpdateModel.Ring}"/>
<Setter Property="Header" Value="{Binding DisplayName}"/>
<Setter Property="ToolTip" Value="{Binding ToolTip}"/>
</Style>
</MenuItem.ItemContainerStyle>
The item source for the parent menu item was bound to an MarkupExtension implementation that provided values for each member in the enum.
Now, when the menu item was checked, I used this code to set the value of the property without removing the binding.
menuItem.Checked += (sender, args) =>
{
var checkedMenuItem = (MenuItem)sender;
var targetEnum = checkedMenuItem.GetValue(EnumTargetProperty);
var menuItemValue = checkedMenuItem.GetValue(EnumValueProperty);
if (targetEnum != null && menuItemValue != null)
{
var bindingExpression = BindingOperations.GetBindingExpression(d, EnumTargetProperty);
if (bindingExpression != null)
{
var enumTargetObject = bindingExpression.ResolvedSource;
if (enumTargetObject != null)
{
var propertyName = bindingExpression.ResolvedSourcePropertyName;
if (!string.IsNullOrEmpty(propertyName))
{
var propInfo = enumTargetObject.GetType().GetProperty(propertyName);
if (propInfo != null)
{
propInfo.SetValue(enumTargetObject, menuItemValue);
}
}
}
}
}
};
This seems to work fine for my scenario with a complex path.
I hope this helps out!
Here is how I've done for a TextBlock:
BindingExpression bindingExpression = textBlock.GetBindingExpression(TextBlock.TextProperty);
string propertyName = bindingExpression.ResolvedSourcePropertyName;
PropertyInfo propertyInfo;
Type type = bindingExpression.DataItem.GetType();
object[] indices = null;
if (propertyName.StartsWith("[") && propertyName.EndsWith("]"))
{
// Indexed property
propertyInfo = type.GetProperty("Item");
indices = new object[] { propertyName.Trim('[', ']') };
}
else
propertyInfo = type.GetProperty(propertyName);
if (propertyInfo.PropertyType == typeof(string))
{
propertyInfo.SetValue(bindingExpression.DataItem, text, indices);
// To update the UI.
bindingExpression.UpdateTarget();
}
I am trying to validate my model class using IDataErrorInfo as given below.
//Validators
public string this[string propertyName] {
get {
string error = null;
if (propertyName == "Name") {
error = ValidateName();
}
return error;
}
}
This works fine, except that when the view first loads it already contains validation errors. Is it possible to ignore/suppress validation errors when the view is first loaded. Also, is it a common practice to show errors when the view loads and before the User starts the data entry for the model properties.
regards,
Nirvan.
Edit:
This is how I am setting up IDataErrorInfo.
<TextBox Text="{Binding Name, ValidatesOnDataErrors=True}" Grid.Row="1" Grid.Column="1" />
I have took the following approach and it works. Basically, the Model should rightfully record errors and have them listed up in a dictionary, even if the object is just instantiated and User hasn't entered any Text yet. So I didn't change my Model code or the IDataErrorInfo validation code. Instead, I just set the Validation.Error Template property to {x:Null} initially. Then there is code to wire up the TextBox's LostFocus event that changes the Validation.Error template back to what I am using. In order to achieve the swapping of templates and attaching LostFocus event handler to all TextBox's in my application, I used a couple of dependency properties. Here is the code that I have used.
Dependency Properties and LostFocus Code:
public static DependencyProperty IsDirtyEnabledProperty = DependencyProperty.RegisterAttached("IsDirtyEnabled",
typeof(bool), typeof(TextBoxExtensions), new PropertyMetadata(false, OnIsDirtyEnabledChanged));
public static bool GetIsDirtyEnabled(TextBox target) {return (bool)target.GetValue(IsDirtyEnabledProperty);}
public static void SetIsDirtyEnabled(TextBox target, bool value) {target.SetValue(IsDirtyEnabledProperty, value);}
public static DependencyProperty ShowErrorTemplateProperty = DependencyProperty.RegisterAttached("ShowErrorTemplate",
typeof(bool), typeof(TextBoxExtensions), new PropertyMetadata(false));
public static bool GetShowErrorTemplate(TextBox target) { return (bool)target.GetValue(ShowErrorTemplateProperty); }
public static void SetShowErrorTemplate(TextBox target, bool value) { target.SetValue(ShowErrorTemplateProperty, value); }
private static void OnIsDirtyEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) {
TextBox textBox = (TextBox)dependencyObject;
if (textBox != null) {
textBox.LostFocus += (s, e) => {
if ((bool) textBox.GetValue(ShowErrorTemplateProperty) == false) {
textBox.SetValue(ShowErrorTemplateProperty, true);
}
};
}
}
If IsDirtyEnabled dependency property is set to true, it uses the callback to attach the TextBox's LostFocus event to a handler. The handler just changes the ShowErrorTemplate attached property to true, which in turn triggers in textbox's style trigger to show the Validation.Error template, when the TextBox loses focus.
TextBox Styles:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationErrorTemplate}"/>
<Setter Property="gs:TextBoxExtensions.IsDirtyEnabled" Value="True" />
<Style.Triggers>
<Trigger Property="gs:TextBoxExtensions.ShowErrorTemplate" Value="false">
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
This may seem too much of code for a simple thing, but then I have to do it only once for all the Textboxes I am using.
regards,
Nirvan.
try to set the data context AFTER the view has been shown.
In my case, this helped.
Are you throwing the exception in the get?
public string Name
{
get { return _name; }
set
{
_name = value;
if (String.IsNullOrEmpty(value))
{
throw new ApplicationException("Customer name is mandatory.");
}
}
}
you set the rules in your ValidateName() method. your view just show the error :) i suggest that name is a mandatory field and should be filled by the user but you dont like the red border when the view is first loaded?
i use two different control templates for validation.errortemplate the normal one and one for mandatory fields (a red *)
thats the way i did it the last time.
This cannot be this difficult. The TreeView in WPF doesn't allow you to set the SelectedItem, saying that the property is ReadOnly. I have the TreeView populating, even updating when it's databound collection changes.
I just need to know what item is selected. I am using MVVM, so there is no codebehind or variable to reference the treeview by. This is the only solution I have found, but it is an obvious hack, it creates another element in XAML that uses ElementName binding to set itself to the treeviews selected item, which you must then bind your Viewmodel too. Several other questions are asked about this, but no other working solutions are given.
I have seen this question, but using the answer given gives me compile errors, for some reason I cannot add a reference to the blend sdk System.Windows.Interactivity to my project. It says "unknown error system.windows has not been preloaded" and I haven't yet figured out how to get past that.
For Bonus Points: why the hell did Microsoft make this element's SelectedItem property ReadOnly?
You should not really need to deal with the SelectedItem property directly, bind IsSelected to a property on your viewmodel and keep track of the selected item there.
A sketch:
<TreeView ItemsSource="{Binding TreeData}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which
// also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree,
// otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
static virtual void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
A very unusual but quite effective way to solve this in a MVVM-acceptable way is the following:
Create a visibility-collapsed ContentControl on the same View the TreeView is. Name it appropriately, and bind its Content to some SelectedSomething property in viewmodel. This ContentControl will "hold" the selected object and handle it's binding, OneWayToSource;
Listen to the SelectedItemChanged in TreeView, and add a handler in code-behind to set your ContentControl.Content to the newly selected item.
XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
Code Behind:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
ViewModel:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
You can create an attached property that is bindable and has a getter and setter:
public class TreeViewHelper
{
private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TreeView))
return;
if (!behaviors.ContainsKey(obj))
behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));
TreeViewSelectedItemBehavior view = behaviors[obj];
view.ChangeSelectedItem(e.NewValue);
}
private class TreeViewSelectedItemBehavior
{
TreeView view;
public TreeViewSelectedItemBehavior(TreeView view)
{
this.view = view;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
}
internal void ChangeSelectedItem(object p)
{
TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
item.IsSelected = true;
}
}
}
Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise. This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.
Use the OneWayToSource binding mode. This doesn't work. See edit.
Edit: Looks like this is a bug or "by design" behavior from Microsoft, according to this question; there are some workarounds posted, though. Do any of those work for your TreeView?
The Microsoft Connect issue: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Posted by Microsoft on 1/10/2010 at 2:46 PM
We cannot do this in WPF today, for the same reason we cannot support
bindings on properties that are not DependencyProperties. The runtime
per-instance state of a binding is held in a BindingExpression, which
we store in the EffectiveValueTable for the target DependencyObject.
When the target property is not a DP or the DP is read-only, there's
no place to store the BindingExpression.
It's possible we may some day choose to extend binding functionality
to these two scenarios. We get asked about them pretty frequently. In
other words, your request is already on our list of features to
consider in future releases.
Thanks for your feedback.
I decided to use a combination of code behind and viewmodel code. the xaml is like this:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
Code behind
private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
if (vm != null)
{
var treeItem = sender as TreeView;
vm.TreeItemSelected = treeItem.SelectedItem;
}
}
And in the viewmodel there is a TreeItemSelected object which you can then access in the viewmodel.
You can always create a DependencyProperty that uses ICommand and listen to the SelectedItemChanged event on the TreeView. This can be a bit easier than binding IsSelected, but I imagine you will wind up binding IsSelected anyway for other reasons. If you just want to bind on IsSelected you can always have your item send a message whenever IsSelected changes. Then you can listen to those messages anyplace in your program.
Considering the code below:
xmlns:interactivity="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
...
<ToggleButton IsChecked="{Binding Path=IsGlobalControllerAttached}" Command="{Binding Path=AttachDetachGlobalControllerAction}" ToolTip="{Binding Path=GlobalControllerToolTip}" Visibility="{Binding Path=CanApplyDateFilter, Converter={StaticResource bool2VisibilityConverter}}" Style="{StaticResource toolBarToggleButton}">
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsGlobalControllerCreated}" Value="true" TrueState="Normal" FalseState="Disabled" />
</i:Interaction.Behaviors>
<Image Source="../../Common/Images/pin.png"/>
</ToggleButton>
I am trying to set VisualState of Toggle Button by binding it to some property in ViewModel.
Here, I am not able to find the Microsoft.Expression.Interactivity.dll in the "Add Reference" list. I am using VS 2010.
What am i missing? Do i need to install Expression blend to get this dll?
Also,
Is there any other way to get the job done? ( Changing VisualState of a control by biding it with some property of ViewModel).
Thanks for your interest.
We use Attached Properties to manage custom state changes on elements. These are then just bound to the view model.
e.g. for a "split screen" setting we do the following.
Create a DependancyProperty in a class called SplitScreen, with a property called Mode:
public class SplitScreen
{
public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register("Mode",
typeof(SplitScreenMode),
typeof(UserControl),
new PropertyMetadata(SplitScreenMode.None,
new PropertyChangedCallback(OnScreenModeChanged)));
public static void SetMode(DependencyObject obj, SplitScreenMode value)
{
obj.SetValue(ModeProperty, value);
}
public static SplitScreenMode GetMode(Control obj)
{
return (SplitScreenMode)obj.GetValue(ModeProperty);
}
static void OnScreenModeChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as UserControl;
if (control != null)
{
if (control.Parent == null)
{
control.Loaded += (s, e) =>
{
ApplyCurrentState(control);
};
}
else
{
ApplyCurrentState(control);
}
}
}
[snip]
}
You might note our little trick to late-update the value when Attached Property is initially set (there is often no parent element until the page is fully loaded).
In the Xaml file attach the property to the required element like this:
lib:SplitScreen.Mode="{Binding SplitScreenMode}"
The key is to catch dependency property changes and get that to change the visual state of the attached element (this is the snipped part of the SplitScreen.cs file):
static public void ApplyCurrentState(Control control)
{
string targetState;
switch (GetMode(control))
{
case SplitScreenMode.Single:
targetState = SplitScreenModeName.Single;
break;
case SplitScreenMode.Dual:
targetState = SplitScreenModeName.Dual;
break;
default:
targetState = SplitScreenModeName.None;
break;
}
VisualStateManager.GoToState(control, targetState, true);
}
The alternative is to install the Expression Blend SDK
You do not need Expression Blend to make use of the SDK and all the cool extras. It is a lot less work for simple items (we just needed some custom behaviour it did not support).