2

Sooner or later any wpf programmer begin to use BindingProxy.

I am trying to split xaml by moving some of resources into separate resource dictionary. My problem is that resources contain reference to BindingProxy.

How can I handle this situation?

As an example, lets say there is a resource with BindingProxy which is used somewhere

<Window.Resources>
    <local:BindingProxy x:Key="proxy" />
    <ControlTemplate x:Key="test">
        <TextBlock Text="{Binding DataContext.Test, Source={StaticResource proxy}}" />
    </ControlTemplate>
</Window.Resources>
<Control Template="{StaticResource test}" />

and code behind

public partial class MainWindow : Window
{
    public string Test { get; set; } = "Test 123";

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }
}

It may not be the best example, using BindingProxy is not really justified, but it serve demonstration purpose well. During run-time window with text "Test 123" will be shown.


Now lets try move resource to resource dictionary Dictionary1.xaml

<ResourceDictionary ... >
    <ControlTemplate x:Key="test">
        <TextBlock Text="{Binding Test, Source={StaticResource proxy}}"  /> <!-- error here -->
    </ControlTemplate>
</ResourceDictionary>

and change main window resource to

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml" />
        </ResourceDictionary.MergedDictionaries>
        <local:BindingProxy x:Key="proxy" />
    </ResourceDictionary>
</Window.Resources>
<Control Template="{StaticResource test}" />

will lead to desinger and run-time exception

System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '5' and line position '20'.'
Inner Exception
Exception: Cannot find resource named 'proxy'. Resource names are case sensitive.

How can I reference proxy? Is there another technique exist to reference somethining from resource dictionary? Maybe some kind of RelativeResource approach but for things which are not in visual tree? I can't move proxy into ResourceDictionary1.xaml for obvious reasons: it will not capture DataContext of window.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • Maybe you could use dynamic resource... other than that, you got the hierarchy reversed. With a merged dictionary, you can use the resources from within referenced dictionaries in your current scope. But you can't really use current scope resources within the merged dictionaries. – grek40 Dec 03 '20 at 08:36
  • Can you show how proxy is instantiate. If the proxy is singleton, you can use ServiceLocator pattern. – vernou Dec 03 '20 at 11:47
  • Could you extend your example to a simple case, where the proxy is actually needed? Right now, you could always just skip the whole proxy and bind to the implicitely available data context. I have like 3 different ideas how the situation could be solved, but I can't decide on the right way with the presented examples. – grek40 Dec 03 '20 at 16:12
  • @grek40, in article it's `DataGridTextColumn`, in my case it's `GridViewColumn` (see related question). Anything what is not a part of visual tree will do, e.g. [ContextMenu](https://stackoverflow.com/q/3583507/1997232), if I recall correctly `Popup`, etc. There you can't use `RelativeSource` and need somehow to pass `DataContext` . – Sinatr Dec 03 '20 at 16:23
  • FYI the `BindingProxy` is a shortcut when you are in a rush but it bypasses completely the MVVM pattern and wouldn't be recommended as it would become very hard to unit-test. I used it in previous projects and sooner or later it becomes more an obstacle than a benefit. – Lucky Brain Dec 03 '20 at 18:55
  • 2
    @Sinatr I answered your other question instead, since I think it was a bit less of an XY question. I mostly agree with LuckyBrain. The binding proxy is something I found useful in a very limited set of circumstances. Most of the time there is a better way and when the connection between the proxy and its users (some template resource) becomes unclear, then it's time to move on instead of trying to fix what never was meant to exist. – grek40 Dec 03 '20 at 19:13
  • @LuckyBrain, unit testing views? – Sinatr Dec 04 '20 at 08:06
  • @grek40, how does "better way" or "move on" looks like? Taking article as example, how would you solve `DataGridTextColumn.Visibility` binding? [This](https://stackoverflow.com/a/38828916/1997232) or [this](https://stackoverflow.com/a/8847705/1997232) solution don't use proxy, but will have the same problem when attempting to split xaml into part. – Sinatr Dec 04 '20 at 08:11
  • @Sinatr - Answering your question "Unit testing views?": That's exactly the point, you need to unit test the presentation logic implemented in the viewmodel which is completely separated from the view designed by the designer. The designer just goes to the point of a beautiful and functional design without logic in the view. The programmer applies in the viewmodel all the logic necessary to provide anything the designer is expecting. This separation of concerns is very important for efficient maintenance and the reliability of the system relies in unit-testing the view-model. – Lucky Brain Dec 04 '20 at 14:35
  • Also, there are multiple versions to implement the `BindingProxy`, which one are you using? Is you are not specifying its `DataSource` property I assume the data context is embedded in the proxy which is not very flexible and you also need to bind in the target to a property of the `BindingProxy`, typically a container of the actual bound object; in your case, `Text` has to be a property of the `BindingProxy` itself. Can you please provide your implementation of the `BindingProxy` and I could be able to help? – Lucky Brain Dec 04 '20 at 14:56
  • @LuckyBrain, just add `Data="{Binding}"` to `proxy` and use implementation from the article. I wonder how properties of `BindingProxy` would help. My latest idea (didn't tested it yet) was to use attached property to pass `DataContext` from one place to another somehow. – Sinatr Dec 04 '20 at 15:10
  • @Sinatr: thanks, I realized that link you provided right after writing my comment :) – Lucky Brain Dec 04 '20 at 15:31

1 Answers1

5

Even though I don't recommend the BindingProxy in MVVM, this is how I think your problem is resolved:

  • Bear in mind when you include a ResourceDictionary in your view XAML, it automatically inherits the DataContext of the view hence you can keep the BindingProxy in the ResourceDictionary but you need to specify the binding explicitly.
  • Remember to also remove the proxy declaration from the View XAML as it is now in the dictionary.
  • You lose the ability to change the DataContext of the BindingProxy, it will use the DataContext of the consumer view.

ResourceDictionary:

<ResourceDictionary ...>
    <!-- NOTE: Data property grabs the DataContext of the consumer view -->
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
    <ControlTemplate x:Key="text">
        <TextBlock Text="{Binding Data.Test, Source={StaticResource proxy}}" />
    </ControlTemplate>
</ResourceDictionary>

Window:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

Snapshot:

enter image description here


Do you really need the BindingProxy?

The suggestion above defeats the purpose of the BindingProxy as it is not needed anymore; note you could just change the ResourceDictionary as follows and it works exactly the same without any need of the BindingProxy:

<ResourceDictionary ...>
    <ControlTemplate x:Key="test">
        <TextBlock Text="{Binding Test}" />
    </ControlTemplate>
</ResourceDictionary>
Lucky Brain
  • 1,551
  • 12
  • 14
  • Yes, I really need `BindingProxy`. It's just getting on my nerves to ask so many questions showing source code with `GridViewColumn`. Have you tested your solution? I have tried myself moving `proxy` to resource dictionary and it didn't worked, perhaps I've made a mistake, will check it again. – Sinatr Dec 07 '20 at 08:27
  • 1
    Of course I tested it, it works 100%: VS2019, .NET Core 3.1. Put the resources exactly as I say. Are you including the attribute `Data="{Binding}"` in the proxy? I modified the answer to include snapshot of the working application. – Lucky Brain Dec 07 '20 at 12:12