0

In a more complex implementation of the below, I discovered a strange behavior.

I have a window, containing a UserControl. The UserControl requires a constructor with parameters, and is therefore reinitialized after the Window has called InitializeComponent().

Control XAML

<UserControl x:Class="TEMP5.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="33" d:DesignWidth="48">
    <Grid>
        <Button Content="Click"
                Width="40" Height="25" Margin="4"
                Click="Button_Click"/>
    </Grid>
</UserControl>

Control C# code

public partial class MyControl : UserControl
{
    public string Foo { get; set; }

    public MyControl()
    {
        InitializeComponent();
    }

    public MyControl(string foo): this()
    {
        Foo = foo;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Console.WriteLine($"This method was invoked on: {this.GetHashCode()} with Foo value: \"{Foo}\"");
    }
}

Window XAML

<Window x:Class="TEMP5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TEMP5"
        mc:Ignorable="d"
        Title="MainWindow" Height="auto" Width="auto">
    <local:MyControl x:Name="myControl"/>
</Window>

Window C# code

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Console.WriteLine($"Parameter-less constructor creates control: {myControl.GetHashCode()}");
        myControl = new MyControl("Bar");
        Console.WriteLine($"Control is now referenced: {myControl.GetHashCode()} with Foo: {myControl.Foo}");
    }
}

Console Output

Parameter-less constructor creates control: 66824994
Control is now referenced: 5560998 with Foo: Bar

Clicks the button

This method was invoked on: 66824994 with Foo value: ""

Question

Why is the UserControl instance not changed? The console output shows that the field is updated to reference the result of the "parametered" constructor, yet the button click is invoked on the original instance.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
George Kerwood
  • 1,248
  • 8
  • 19
  • 2
    "*The UserControl requires a constructor with parameters*" - this is already wrong. Implement the Foo property in a way that it can be assigned multiple times. – Clemens May 18 '20 at 08:58
  • 2
    That said, `myControl = new MyControl("Bar");` sets the value of the generated `myControl` field, but does not replace the control in the visual tree. You would also have to set `Content = myControl;` – Clemens May 18 '20 at 08:59
  • XAML-friendly object should be completely usable with a default constructor, so there should be no behaviour only accessible when using non-default constructors (more [here](https://stackoverflow.com/a/26163003/9363973)). You'll want to change your property to a [dependency property](https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-properties-overview) to be able to set `Foo` like so `` – MindSwipe May 18 '20 at 08:59
  • @Clemens I currently have the same misunderstanding as MindSwipe. No doubt there’s something fundamental I’ve missed. Is there some resource you could kindly refer me to? – George Kerwood May 18 '20 at 09:09
  • 1
    Interesting, code behind WPF and WinForms work differently. In WPF Reassigning `myControl` does nothing, but reassigning `Content` to the new `myControl` does update what's shown (just checked in a small program of my own). Where as in WinForms simply reassigning `myControl` would already update what's shown. TIL. Deleting my unnecessary comments to avoid clutter – MindSwipe May 18 '20 at 09:10
  • 1
    @Clemens In fact I think I understand. The myControl field is not direct reference to the Window’s Content? – George Kerwood May 18 '20 at 09:11
  • 1
    `` makes the XAML parser generate a field that references exactly that MyControl instance, nothing more. In order to show a different instance, you have to put it somewhere. Your MainWindow's XAML puts a MyControl in its Content, so that is what you have to replace. – Clemens May 18 '20 at 09:14
  • You may also want to take a look at data templating. See e.g. here: https://stackoverflow.com/q/25568930/1136211 – Clemens May 18 '20 at 09:16
  • Perfect, understood, thank you for your time @Clemens. – George Kerwood May 18 '20 at 09:20

1 Answers1

0

With credit and thanks to @Clemens for the answer provided in comments above. In summary:

Recommendation is to avoid property assignment through a constructor in place of an implementation that allows for reassignment. Suggestion to consider data templating as per: C# WPF content switching.

The observed behavior is due to the fact that the myControl field does not make reference to an instance assigned to the Content property of the Window.

Replacing myControl = new MyControl("Bar"); with Content = new MyControl("Bar"); would result in the expected behavior. However, again, this is not the recommended approach.

Thank you also to @MindSwipe for their contribution.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
George Kerwood
  • 1,248
  • 8
  • 19