-1

I have a UserControl that binds a property to a control, which seems to work:

<UserControl x:Class="MyUserControl" Name="control">
<TextBox Text="{Binding SomeData, ElementName=control}" />

However, fetching the information for this specific property needs another property to be set, which is set after InitializeComponent in the main window. The constructor for my UserControl though gets called in InitializeComponent, causing a NullReferenceException:

public MainWindow() {
   InitializeComponent();           // <-- here my UserControl is instatiated and needs Settings

   Settings = new Settings();    
   MyUserControl.Settings = Settings;    // <-- only here do we set the Settings object (which is needed in UserControl's constructor!
}


public MyUserControl {
    public string SomeData { 
         get {
               return Settings.Get("someSetting");
         }
    }
}

How can I solve this? Or is my architecture wrong (just started out with WPF and data binding).

Bart Friederichs
  • 33,050
  • 15
  • 95
  • 195
  • 1
    `` in XAML calls the parameterless constructor. Redesign your control so that it is able to deal with a Settings property that is set later. – Clemens Mar 28 '20 at 21:35
  • You should bind the `MyUserControl` to the `MainWindow.Settings` property, which should be a `DependencyProperty`. Then move the initialization from the constructor of `MyUserControl` to a `MyUserControl.Loaded` event handler. The bound `Settings` value will be available when this handler is executed. – BionicCode Mar 28 '20 at 22:11
  • You haven't provided sufficient context to know just how far down the wrong path you've already gone, but see marked duplicate for information about various ways to approach the scenario. Short version: your user control needs to expose a property to which the settings data can be bound, gracefully handling that property's value being both null/un-set as well as having a valid value, and binding to that property in XAML. – Peter Duniho Mar 28 '20 at 23:35

1 Answers1

0

You should bind the MyUserControl to the MainWindow.Settings property, which should be a DependencyProperty. Then move the initialization from the constructor of MyUserControl to a MyUserControl.Loaded event handler. The bound Settings value will be available when this handler is executed.

Alternatively define a PropertyChangedCallback.

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static readonly DependencyProperty SettingsProperty = DependencyProperty.Register(
    "Settings",
    typeof(Settings),
    typeof(MainWindow),
    new PropertyMetadata(default(Settings)));

  public Settings Settings
  {
    get => (Settings) GetValue(MainWindow.SettingsProperty);
    set => SetValue(MainWindow.SettingsProperty, value);
  }

  public MainWindow() 
  {
    InitializeComponent();
    this.Settings = new Settings();   
  }
}

MyUserControl.xaml.cs

partial class MyUserControl : UserControl
{
  public static readonly DependencyProperty SettingsProperty = DependencyProperty.Register(
    "Settings",
    typeof(Settings),
    typeof(MyUserControl),
    new PropertyMetadata(default(Settings), OnCurrentReadingChanged));

  public Settings Settings
  {
    get => (Settings) GetValue(MyUserControl.SettingsProperty);
    set => SetValue(MyUserControl.SettingsProperty, value);
  }

  public MyUserControl() 
  {
    InitializeComponent();
    this.Loaded += Initialize;   
  }

  private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    Settings settings = (d as MyUserControl).Settings;
    // TODO: Initialize using this.Settings
  }

  public Initialize(object sender, EventArgs e) 
  {
    // TODO: Initialize using this.Settings
  }
}

MyUserControl.xaml

<UserControl x:Class="MyUserControl" 
             Name="control" 
             Settings="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=Settings}">
  <TextBox Text="{Binding SomeData, ElementName=control}" />
</UserControl>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • This is incorrect. As a general rule, there should be no need to handle the `Loaded` event for this type of data. The data should be bound in XAML to a property exposed by the user control, and the user control should be able to deal with default values as well as new values set later. The framework will then assign the value at its earliest convenience according to the binding, and it all "just works". No need to special-case handle it in the `Loaded` event. – Peter Duniho Mar 28 '20 at 22:49
  • What is _"incorrect"_? Then your advice to call a parameterized constructor from XAML is also incorrect. If you are criticizing his design choices, you should've recommended the use of a view model instead. You just showed him a workaround like I did. Also as you can see, the data **is** _"bound in XAML to a property exposed by the user control"_. I don't know why he is handling this data in the constructor. I just suggested to move the initialization code, that relies on the `Settings` instance, from the constructor to the `Loaded` event - to fix the null reference issue (see the question). – BionicCode Mar 28 '20 at 23:21
  • You closed this question and gave a reference to a topic "Calling a parameterized constructor from XAML" as the solution. I mean, come on - you never said anything I said you said? Seriously? – BionicCode Mar 28 '20 at 23:41
  • Yes, seriously. This question is an exact duplicate of the other. I am neither promoting nor critiquing the answers found in the other question. It is your error to think and claim otherwise. I am simply identifying this question as an exact duplicate. My advice to the OP of _this_ question is contained in the comment I included when I closed the question. If you feel you have a better answer, by all means post it there. But none of that will change my opinion that your proposed solution is no better, and in some cases worse, than the answers found in the duplicate. – Peter Duniho Mar 28 '20 at 23:44
  • I added a second solution which makes use of the `PropertyChangedCallback`. – BionicCode Mar 29 '20 at 00:11