I looked at a lot of topics, but they all in one way or another are related to the definition of the DataContext of UI elements.
I have a task that requires a completely different approach.
And no matter how much I puzzled over the decision, I could not think of anything.
Description of the problem.
Initially, there is a simple proxy:
using System;
using System.Windows;
namespace Proxy
{
/// <summary> Provides a <see cref="DependencyObject"/> proxy with
/// one property and an event notifying about its change. </summary>
public class Proxy : Freezable
{
/// <summary> Property for setting external bindings. </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(Proxy), new PropertyMetadata(null));
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
}
}
If you set it in the Resources of any element, then it can get the DataContext with a simple Binding:
<FrameworkElement.Resources>
<proxy:ProxyValue x:Key="proxy"
Value="{Binding}"/>
</FrameworkElement.Resources>
Likewise, any Bindig without an explicitly specified source will use the DataContext of the element in whose resources the proxy instance is declared as the source.
Child proxy injection.
Now, for a certain task (its conditions are not relevant to the question, so I will not describe it) I needed a nested (child) proxy which can also be assigned a binding relative to the data context.
And I need to set this binding in code.
A highly simplified example for demonstration:
using System.Windows.Data;
namespace Proxy
{
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
Binding binding = new Binding();
BindingOperations.SetBinding(this, ValueProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
}
}
<StackPanel DataContext="Some data">
<FrameworkElement.Resources>
<proxy:PregnantProxy x:Key="proxy"/>
</FrameworkElement.Resources>
<TextBlock Text="{Binding}" Margin="10"/>
<TextBlock Text="{Binding Value, Source={StaticResource proxy}}" Margin="10"/>
<TextBlock Text="{Binding Child.Value, Source={StaticResource proxy}}" Margin="10"/>
</StackPanel>
Parent proxy binding works as expected.
But linking a child will not return anything.
How can you set the correct binding for a child?
"If you set it in the Resources of any element, then it can get the DataContext with a simple Binding" - this is the crucial mistake. Resource dictionary has not the DataContext inheritance. You can easy see it, if you add to the resource dictionary e.g. a Label and try to use binding for it(see example below).
That it works for Text="{Binding Value, Source={StaticResource proxy}}" lays on inheritance from Freezable class, which finds out data context and use for it if I'm not mistaken Freezable.ContextList, which is private see implementation of Freezable. This implementation doesn't work for Child, since it's not in a resource dictionary.
So if you do inherit not from Freezable, but from let us say it's parent class DependencyObject, also Text="{Binding Value, Source={StaticResource proxy}}" will not work.
I don't know for what you need this construction, it looks for me a kind of weird, but if you inherit from FrameworkElement and provide a DataContext for the proxy and it's child element (in XAML you can hard code it, or use StaticResource or custom MarkupExtension for it) it can work. See modified code.
public class Proxy : FrameworkElement
{
/// <summary> Property for setting external bindings. </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(Proxy), new PropertyMetadata(null));
//protected override Freezable CreateInstanceCore()
//{
// throw new NotImplementedException();
//}
}
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
var binding = new Binding() {};
BindingOperations.SetBinding(this, ValueProperty, binding);
//Child
this.AddLogicalChild(Child);
BindingOperations.SetBinding(Child, DataContextProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
}
and in XAML accordingly:
<StackPanel DataContext="Some data">
<StackPanel.Resources>
<local:PregnantProxy x:Key="proxyResBinding" DataContext="{Binding}"/>
<local:PregnantProxy x:Key="proxyHardCodedDC" DataContext="Proxy hardcoded DC"/>
<Label x:Key="lblResBinding" DataContext="{Binding}"/>
<Label x:Key="lblHardcoded" DataContext="hard coded DC"/>
</StackPanel.Resources>
<Label Content="{Binding}" Background="Yellow" />
<Label Content="{Binding Child.Value, Source={StaticResource proxyResBinding}}" Background="Red"/>
<Label Content="{Binding Value, Source={StaticResource proxyResBinding}}" Background="Red"/>
<Label Content="{Binding Child.Value, Source={StaticResource proxyHardCodedDC}}" Background="Green"/>
<Label Content="{Binding Value, Source={StaticResource proxyHardCodedDC}}" Background="Green"/>
<Label Content="{Binding DataContext, Source={StaticResource lblResBinding}}" Background="Red"/>
<Label Content="{Binding DataContext, Source={StaticResource lblHardcoded}}" Background="Green"/>
</StackPanel>
For now, I've implemented a working solution with finding the parent FrameworkElement and adding a child proxy to the resources of the parent FrameworkElement.
The test use case shown in the question works correctly.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Proxy
{
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
BindingOperations.SetBinding(this, ParentProperty, FindAncestorFrameworkElement);
Binding binding = new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(StackPanel), 1) };
BindingOperations.SetBinding(this, ValueProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
/// <summary>
/// Родительский FrameworkElement
/// </summary>
public FrameworkElement Parent
{
get { return (FrameworkElement)GetValue(ParentProperty); }
set { SetValue(ParentProperty, value); }
}
/// <summary><see cref="DependencyProperty"/> для свойства <see cref="Parent"/>.</summary>
public static readonly DependencyProperty ParentProperty =
DependencyProperty.Register(nameof(Parent), typeof(FrameworkElement), typeof(PregnantProxy), new PropertyMetadata(null, ParentChanged));
private static void ParentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PregnantProxy proxy = (PregnantProxy)d;
if (e.OldValue is FrameworkElement oldElement)
{
oldElement.Resources.Remove(proxy.key);
}
if (e.NewValue is FrameworkElement newElement)
{
double key;
do
{
key = random.NextDouble();
} while (newElement.Resources.Contains(key));
newElement.Resources.Add(proxy.key = key, proxy.Child);
}
if (!Equals(BindingOperations.GetBinding(proxy, ParentProperty), FindAncestorFrameworkElement))
BindingOperations.SetBinding(proxy, ParentProperty, FindAncestorFrameworkElement);
}
private double key;
private static readonly Random random = new Random();
private static readonly Binding FindAncestorFrameworkElement = new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(FrameworkElement), 1) };
}
}
But such a solution may throw an exception if the resources are locked or read-only.
The final solution.
The answer from #Rekshino pushed me to look for a solution in a different direction.
I created an extension method to set a DependencyObject's context relative to another DependencyObject.
The method can be applied to any DependecyObject.
But context-relative bindings are only interpreted by Freezable.
So it makes little sense for the rest of the DependecyObject.
But maybe I missed something, and I can somehow use it or modify it.
using System;
using System.Linq;
using System.Reflection;
using System.Windows;
namespace Proxy
{
public static class ProxyExtensionMethods
{
private static readonly Func<DependencyObject, DependencyObject, DependencyProperty, bool> ProvideSelfAsInheritanceContextHandler;
static ProxyExtensionMethods()
{
var methods = typeof(DependencyObject)
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
MethodInfo method = null;
foreach (var meth in methods
.Where(m => m.Name == "ProvideSelfAsInheritanceContext" &&
m.ReturnType == typeof(bool)))
{
var parameters = meth.GetParameters();
if (parameters?.Length == 2 &&
typeof(DependencyObject) == parameters[0].ParameterType &&
typeof(DependencyProperty) == parameters[1].ParameterType)
{
method = meth;
break;
}
}
ProvideSelfAsInheritanceContextHandler = (Func<DependencyObject, DependencyObject, DependencyProperty, bool>)
method
.CreateDelegate
(
typeof(Func<DependencyObject, DependencyObject, DependencyProperty, bool>)
);
}
/// <summary>Sets the DependecyObject context</summary>
/// <param name="obj">The object for which the Context is to be set.</param>
/// <param name="context">The object to be used as the Context.</param>
public static void SetContext(this DependencyObject obj, DependencyObject context)
{
ProvideSelfAsInheritanceContextHandler(context, obj, PrivateKey.DependencyProperty);
}
private static readonly DependencyPropertyKey PrivateKey=
DependencyProperty.RegisterAttachedReadOnly("Private", typeof(object), typeof(ProxyExtensionMethods), new PropertyMetadata(null));
}
}
Usage example.
using System.Windows.Data;
namespace Proxy
{
public class PregnantProxy : Proxy
{
public Proxy Child { get; } = new Proxy();
public PregnantProxy()
{
Child.SetContext(this);
Binding binding = new Binding() { };
BindingOperations.SetBinding(this, ValueProperty, binding);
BindingOperations.SetBinding(Child, ValueProperty, binding);
}
}
}
The XAML shown in the question works correctly with such an implementation.
If someone has comments on the code and possible problems with such an implementation, I am ready to listen carefully.
I have a view which wraps a TreeView, called MbiTreeView. I want to get the selected item from the (wrapped) tree view in the view model.
The 'parent' user control which uses this custom user control:
<UserControl [...]>
<views:MbiTreeView
Grid.Row="0"
cal:Bind.Model="{Binding TreeViewModel}"
SelectedItem="{Binding SelectedItem}">
</views:MbiTreeView>
</UserControl>
The parent user control is bound to this view model:
internal sealed class SomeViewModel : PropertyChangedBase
{
public object SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
NotifyOfPropertyChange(() => SelectedItem);
}
}
public IMbiTreeViewModel TreeViewModel { get; }
public SomeViewModel(
IMbiTreeViewModel treeViewModel)
{
TreeViewModel = treeViewModel;
}
}
The MbiTreeView user control is rather straight forward. It subscribes to the selection changed event, and defines a few templates (not relevant for this question, so left them out in the question)
<TreeView ItemsSource="{Binding Items}" SelectedItemChanged="TreeView_OnSelectedItemChanged">
iew.ItemContainerStyle>
The code behind declares the dependency property:
public partial class MbiTreeView
{
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
null);
public object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
public MbiTreeView()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
}
when I start the application, I can navigate through the tree view items. When I click on a treeview node, then the OnSelectedItemChanged event fires (I get into my breakpoint there). So everything goes fine up and until setting the value in the dependency property SelectedItem.
Then I would expect that the xaml binding gets notified, and updates the view model. But that never happens.
I am getting nowhere with this, help is greatly appreciated.
The SelectedItem Binding should be TwoWay:
<views:MbiTreeView ...
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
You could declare the property like shown below to make to bind TwoWay by default.
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I cannot properly bind to a UserControl property placed in a Page.
I have this UserControl :
<UserControl x:Class="xxxx.NumericBox" (...)>
<TextBox Name="TextBoxValue" Text="{Binding RelativeSource {RelativeSource AncestorType=UserControl}, Path=Value, Mode=TwoWay}" (...)
With this behind code :
public partial class NumericBox : UserControl
{
public NumericBox()
{
InitializeComponent();
}
public uint? Value
{
get => (uint?)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(uint?), typeof(NumericBox), new PropertyMetadata(null));
The UserControl contains others controls witch interract with Value property (+/-) and it works fine.
But I create the DependencyProperty to also bind the value in parent page.
A exemple of code in a page where I inject the UserControl :
var binding = new Binding("Line.Quantity");
binding.Mode = BindingMode.TwoWay;
var numeric = new NumericBox();
numeric.SetBinding(ValueProperty, binding);
The binding works on startup but not update Line.Quantity when I modify the Textbox...
The Line class implements INotifyPropertyChanged and notify change on Quantity.
What is the correct way to do that ?
I have seen this question but but I have not been able to correct my code :
Binding on DependencyProperty of custom User Control not updating on change
I try to create Command which inherit from DependencyObject and ICommand. I have the following code:
public class CustomCommand : DependencyObject, ICommand
{
public static readonly DependencyProperty CommandProperty;
public static readonly DependencyProperty AfterCommandProperty;
static CustomCommand()
{
var ownerType = typeof(CustomCommand);
CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(Action), ownerType, new PropertyMetadata(null));
AfterCommandProperty = DependencyProperty.RegisterAttached("AfterCommand", typeof(Action), ownerType, new PropertyMetadata(null));
}
public Action Command
{
get => (Action)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public Action AfterCommand
{
get => (Action)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
// Command & AfterCommand are always null
}
}
and
<Button Content="Test">
<Button.Command>
<command:CustomCommand Command="{Binding Copy}" AfterCommand="{Binding AfterCopy}" />
</Button.Command>
</Button>
When I press Test button Command and AfterCommand are null. Do you have an idea ? What is the best way cause I can't add ICommand reference to my ViewModel.
Thanks
Your CustomCommand instance isn't in the visual tree, so binding is a problem. It's got no way to get a DataContext. Try putting a trace on the binding:
<Button.Command>
<local:CustomCommand
Command="{Binding TestAction, PresentationTraceSources.TraceLevel=High}"
/>
</Button.Command>
"Framework mentor not found" is the error you'll see in the debug output. "Ya can't get there from here" is how they'd say that Down East. Context in XAML is a matter of parent-to-parent, but this thing has, in the sense that matters here, no parent.
But it's an easy fix. Use a binding proxy. Here's a town bike implementation that I've stolen several times from various questions and answers on Stack Overflow:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Define an instance as a resource in some containing scope that has the DataContext where the desired Action property lives. {Binding} with no path just returns DataContext, which will be the window's viewmodel in the case below.
<Window.Resources>
<local:BindingProxy
x:Key="MainViewModelBindingProxy"
Data="{Binding}"
/>
</Window.Resources>
And use it like so. The Data property of BindingProxy is bound to the viewmodel, so use a path of Data.WhateverPropertyYouWant. I called my Action property TestAction.
<Button
Content="Custom Command Test"
>
<Button.Command>
<local:CustomCommand
Command="{Binding Data.TestAction, Source={StaticResource MainViewModelBindingProxy}}"
/>
</Button.Command>
</Button>
N.B.
You've also got a bug in your AfterCommand property: It's passing CommandProperty to GetValue/SetValue, not AfterCommandProperty.
I have a UserControl called ActionsTreeView I built using MVVM practices where I have an IPluginsProvider interface that populates the data in my UserControl. I want to be able to provide an object implementating this IContentProvider interface as a parameter to initialize my UserControl's ViewModel.
Here is my approach so far, which isn't working. I am wondering if I'm going down the right path? I declare a DependencyProperty in my user control which is visible to my mainWindow where I want to instantiate this UserControl. This code just attempts to pass the PluginsProvider object to my UserControl which needs it to build its ViewModel.
My PluginProvider DependencyProperty setter in my UserControl never gets hit because my My PropertyChanged handler is always null in MainWindow.xaml.cs I think I have the code right, but not sure I'm going down the right road and what I'm missing to make this connection?
ActionsTreeView.xaml.cs
public partial class ActionsTreeView: UserControl
{
public static readonly DependencyProperty PluginProviderProperty = DependencyProperty.Register("PluginProvider", typeof(Models.IPluginsProvider), typeof(ActionsTreeView), new FrameworkPropertyMetadata(null, OnPluginProviderChanged));
private ViewModels.ActionsTreeViewModel vm;
public ActionsTreeView()
{
//Wire-up our ViewModel with the data provider and bind it to DataContext for our user control
//This is a Mock-up until I figure out a way to get the real provider here
Models.IPluginProvider pluginSource = new Models.MockPluginProvider();
vm = new ViewModels.ActionsTreeViewModel(pluginSource );
this.DataContext = vm;
InitializeComponent();
}
private static void OnPluginProviderChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((ActionsTreeView)source).PluginProvider = (Models.IPluginsProvider)e.NewValue;
}
public Models.IPluginsProvider PluginProvider
{
get
{
return (Models.IPluginsProvider)GetValue(PluginProviderProperty);
}
set
{
SetValue(PluginProviderProperty, value);
vm.SetPluginSource(PluginProvider);
}
}...
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
}
private Models.IPluginsProvider _actionProvider;
public Models.IPluginsProvider ActionProvider
{
get { return _actionProvider; }
set
{
_actionProvider = value;
OnPropertyChanged("ActionProvider");
}
}
protected void OnPropertyChanged(string property)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) //HANDLER IS ALWAYS NULL
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
Using my UserControl in MainWindow.xaml
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider}" />
</Grid>
I don't think you can pass a parameter in the ctor in xaml.
If you create control in code behind you can pass the parameter in the ctor(Param param)
Not sure if this fits in the MVVM model but I use it a lot in regular code behind
Use a frame in the XAML for a place to put the UserControl
Seems like you are missing the binding source
<Grid>
<UserControls:ActionsTreeView PluginProvider="{Binding ActionProvider, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" />
</Grid>
since your property ActionProvider is declared in MainWindow so during binding you are required to refer the same source unless you've set it as data context of the window
alternative to above you can also do the below if there is no other data context used in the MainWindow then you can use the original binding you have PluginProvider="{Binding ActionProvider}"
public MainWindow()
{
InitializeComponent();
this.ActionProvider = new Models.PluginsProvider(Library.Action.AvailableActions);
DataContext = this;
}
I've set the DataContext to this which will effectively resolve the value of ActionProvider in binding from the instance this
Extra
you may also choose to remove INotifyPropertyChanged from MainWindow as it is already DependencyObject and capable of property notification and declare a DependencyProperty for ActionProvider
eg
public Models.IPluginsProvider ActionProvider
{
get { return (Models.IPluginsProvider)GetValue(ActionProviderProperty); }
set { SetValue(ActionProviderProperty, value); }
}
// Using a DependencyProperty as the backing store for ActionProvider. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActionProviderProperty =
DependencyProperty.Register("ActionProvider", typeof(Models.IPluginsProvider), typeof(MainWindow), new PropertyMetadata(null));
so you don't need to worry for the notification change manually, you might be required to use this if the above solution does not work for you otherwise it is good to have.