2

First off, of course I'm aware that this is one of the most repetitive questions to ask about... i'll save you a moment by saying that this has a twist.

First, the setup

<UserControl {d:DesignInstance Type=MyControl, IsDesignTimeCreatable=True}>
    <Grid x:Name="root">
        <FrameElement x:Name="ContextProxy" Visibility="Collapsed" />
        ...
        <DataGridTextColumn.Visibility>
            <!-- squiggly line on the SOURCE part -->
            <Binding Source="{x:Reference Name=ContextProxy}"
                     Path=DataContext.IncludeThisColumn
                     />
        </DataGridTextColumn.Visibility>
    </Grid>
</UserControl>

codebehind

public partial class MyControl {

    // viewmodel for INPC (data lookups, child controls, etc)
    public ViewModels.vmMyControl { get; } = new ViewModels.vmMyControl();

    MyControl() { // ctor
        this.root.DataContext = this; // to support DependencyProperty
    }

    // several dependency properties for external use/bindings
    public static DP IncludeThisColumnDP = DP.Register(..., OnIncludeThisColumnChanged)
    public bool IncludeThisColumn { GetValue(DP); } { SetValue(DP, value); }

    // relay DP changes from parent / outside world, to internal ViewModel
    private void OnIncludeThisColumnChanged(..)
    { (o as vmMyControl).ViewModel.IncludeThisColumn = e.NewValue; }
}

viewmodel

public class vmMyControl {

    private readonly myObservableINPC<bool> _IncludeThisColumn;
    public bool IncludeThisColumn { get/set for _IncludeThisColumn.Value }

    public vmMyControl() { // ctor
        // initialize INPC crap
        this.IncludeThisColumn = new blah(nameof(IncludeThisColumn));

        // data for designer
        if (...GetIsInDesignMode)
            Data_LoadMock();
    }

    public Data_LoadMock() {
        // populate some of the data points
    }
}

so generally speaking, I think I'm following best practices (other than mixing of DP/INPC, but it addresses my needs well, so whatever)...

  • user control's data context is applied to an internal root element (the top-level Grid)

  • Data context set to instance of usercontrol to enable DP.

  • user control instance has readonly property initialized to ViewModel

  • viewmodel uses GetIsInDesignMode to ensure data doesn't load (and actually, the code's DAL proxies also check an internal bool for indicating when they're being run as a unit test, to further confirm that they're disabled, with InternalsVisibleTo(UnitTestProject))

  • since datagrid column visibility can't be bound directly (not in the element tree or whatever), I added a FrameworkElement to act as a proxy to datacontext


The twist

So, first let me say that at runtime, the code works. All of the bindings, data loads, etc. No exceptions. This is purely a design-time issue, but it bugs me.

Since I'm aware of the null reference issues when dealing w/ WPF design mode, my first action when I saw the issue was to add:

MyControlTests.cs

[TestMethod]
public void InstantiateVM() {
    Action a = () => { return new vmMyControl(); }
    a.ShouldNotThrow(); // FluentAssertions
}

[TestMethod]
public void InstantiateUC() {
    Action a = () => { return new MyControl(); }
    a.ShouldNotThrow(); // FluentAssertions
}

here's the thing... both of the tests run successfully... and upon inspection (debug + breakpoint), no properties in the object result in an exception.


Environment

App is using framework 4.5 and C#6 (since i'm on VS2015)

I saw someone post about VS2015 hovering over the message to get more info, but it doesn't seem to do anything for me.

For what it's worth, I'm using VS2015 REL... was tempted to apply update 2 or 3, but there seem to be a few bugs here and there from what I'm hearing/reading.

Scott Brickey
  • 1,207
  • 12
  • 22
  • also, I've CONFIRMED that the GetIsInDesignMode isn't an issue by adding an early 'return;' in the constructors (in the user control it immediately followed the InitializeComponents call) – Scott Brickey Jul 14 '16 at 15:01
  • I'm starting to wonder if there's anything else in the designer that I need to be included in my unit test... something involved in the initialization or something? (I'm not overriding any of the controls' members) – Scott Brickey Jul 14 '16 at 15:06
  • That's a known VS bug which has been there for ages. It just can't handle `x:Reference` properly. Use a binding with `ElementName=` when possible to work around this issue. – Lucas Trzesniewski Jul 14 '16 at 15:42
  • `this.root.DataContext = this; // to support DependencyProperty` oh lord no. use `{Binding SomeProperty, ElementName=root} where the Control's x:Name is root. You're going to hurt yourself doing that. And the designer is filthy with false errors. Always has been. Don't spend lots of time worrying about them. –  Jul 14 '16 at 16:03
  • @Will it's my understanding that the DataGridColumn.Visibility CAN'T reference by name, since it's not in the visual tree - see http://stackoverflow.com/questions/22073740/binding-visibility-for-datagridcolumn-in-wpf – Scott Brickey Jul 14 '16 at 17:28
  • 1
    Sure, but you're fixing one mistake by (potentially) making another. I'd simply roll the logic for controlling column visibility into the user control itself, then bind whatever signifies visibility state to a DP on the UC, and trigger off changes to it. Might even extend the Grid to create a control that supported this scenario better, if it wasn't working out well within the UC. Anyhow, proceed as best judgement determines, just be very wary about the `DataContext = this;` trap. –  Jul 14 '16 at 18:19
  • As Will pointed out, there are better [alternatives](http://stackoverflow.com/a/37932008/4838058). – Funk Jul 14 '16 at 18:28
  • @Will I found an answer that maintains all the other setup (DPs for external bindings, INPC for internal bindings, etc) and fixes the context. I have no idea why FrameworkElement didn't work the same, but quite frankly don't care at this point :) – Scott Brickey Jul 14 '16 at 19:55

1 Answers1

0

As per http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

public class BindingProxy : Freezable
{
#region Overrides of Freezable

protected override Freezable CreateInstanceCore()
{
    return new BindingProxy();
}

#endregion

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));
}

then

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

and

<DataGridTemplateColumn.Visibility>
    <Binding Source="{StaticResource ResourceKey=DataContextProxy}"
             Path="Data.IncludeThisColumn"
             Converter="{StaticResource ResourceKey=BoolToHiddenConverter}"
             />
</DataGridTemplateColumn.Visibility>
Scott Brickey
  • 1,207
  • 12
  • 22