0
votes

Why is binding for a Caliburn.Micro UserControl enclosed to WPF User Control Library not working with ContentControl?


Initial

Create new Wpf App v4.6.2

  • Install Nuget Caliburn.Micro v3.1.0
  • Install Nuget Caliburn.Micro.Start v3.1.0
  • Do adaptions like explained http://caliburnmicro.com/documentation/nuget
    • adapt app.xaml
    • delete StartupUri
    • add ResourceDictionary
    • check AppBootstrapper
    • delete MainWindow.xaml
  • Add Button x:Name="DoIt" to ShellView.xaml
  • Add public void DoIt() with MessageBox.Show() to ShellViewModel.cs
  • Test this initial version

✓ Check! This runs and binding works...


UserControl View

  • Add UserControl and name it e.g. TestUcView
  • Add a Textbox and give a name e.g. UcValue
<UserControl x:Class="WpfApp1.Test2UcView"
             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" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="50" d:DesignWidth="200" Visibility="{Binding UcVisibility}">
    <Grid >
        <TextBox x:Name="UcValue" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="100"/>
        <Button x:Name="UcAction" Content="Do specific" HorizontalAlignment="Left" Margin="120,10,0,0" VerticalAlignment="Top" Width="75"/>
    </Grid>
</UserControl>


UserControl ViewModel

  • Add class with corresponding name TestUcViewModel
  • Change class to public and derive from screen and add using Caliburn.Micro
  • Add property with corresponding name to TextBox e.g. UcValue
public class Test1UcViewModel : Screen
{
    private string _UcValue;
    public string UcValue { get => _UcValue; set { _UcValue = value; NotifyOfPropertyChange(() => UcValue); } }

    public void TestBut2() {
        MessageBox.Show("TestBut2");
    }
}


UserControl Integration

  • In ShellViewModel create public property for the ViewModel and create an instance in the constructor
  • Create control in View to be bound

    1. Possibility: Place ContentControl in the ShellView.xaml with the same Name as the ViewModel property (ViewModel first)
    2. Possibility: Compile and place the UserControl into the ShellView.xaml and add cal:Bind.Model="{Binding <ViewModel-Property-Name>}" for this the namespace

✓ Check! This runs and binding of UserControl works...


BUT,

...now with integrating the third UserControl that belongs to a WPF User Control Library (dll), the Caliburn binding doesn't work for the UserControl from the dll when using the syntax for ContentControl.

public class ShellViewModel : Caliburn.Micro.PropertyChangedBase, IShell
{
    private Test1UcViewModel _Test1UserControlModel;
    private Test2UcViewModel _Test2UserControlModel;
    private Test3UcViewModel _Test3UserControlModel;

    public Test1UcViewModel Test1 { get => _Test1UserControlModel; set { _Test1UserControlModel = value; NotifyOfPropertyChange(() => Test1); } }
    public Test2UcViewModel Test2 { get => _Test2UserControlModel; set { _Test2UserControlModel = value; NotifyOfPropertyChange(() => Test2); } }
    public Test3UcViewModel Test3 { get => _Test3UserControlModel; set { _Test3UserControlModel = value;NotifyOfPropertyChange(() => Test3); } }

    public ShellViewModel()
    {
        _Test1UserControlModel = new Test1UcViewModel();
        Test1.UcValue = "Bubble";
        _Test2UserControlModel = new Test2UcViewModel();
        Test2.UcValue = "Simmer";
        _Test3UserControlModel = new Test3UcViewModel();
        Test3.Uc3Value = "Knocking on heavens door";
        Test1.UcVisibility = Visibility.Visible;
        Test2.UcVisibility = Visibility.Hidden;
        Test3.UcVisibility = Visibility.Hidden;
    }
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:cal="http://www.caliburnproject.org"
        xmlns:local="clr-namespace:WpfApp1" 
        xmlns:TestUcLib="clr-namespace:TestUcLib;assembly=TestUcLib" 
        x:Class="WpfApp1.ShellView" Width="500" Height="300">

    <Grid Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="Input"
                   TextWrapping="Wrap"
                   VerticalAlignment="Bottom"
                   HorizontalAlignment="Center"
                   FontSize="20" />
        <StackPanel>
            <Button x:Name="DoIt1" Content="Do it 1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="5"/>
            <Button x:Name="DoIt2" Content="Do it 2" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="5"/>
            <Button x:Name="DoIt3" Content="Do it 3" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="5"/>
        </StackPanel>

        <ContentControl x:Name="Test1" Grid.Column="1"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
        <ContentControl x:Name="Test2" Grid.Column="1"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
        <ContentControl x:Name="Test3" Grid.Column="1"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
        <!--This one works-->
        <!--<TestUcLib:Test3UcView cal:Bind.Model="{Binding Test3}" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>-->
    </Grid>

</Window>

Cannot find UserControl

Is it mandatory to use <TestUcLib:Test3UcView cal:Bind.Model="{Binding Test3}" instead of using ContentControl when the UserControl is inside dll?

1

1 Answers

2
votes

Caliburn.Micro uses a simple naming pattern to find the UserControl that it should bind to the view model and display and it only searches any assembly you have exposed as searchable via AssemblySource.Instance: http://caliburnmicro.com/documentation/conventions.

You could override this logic by setting the ViewLocator.LocateForModelType property and implement your own. The following basic example should give you the idea:

public class HelloBootstrapper : BootstrapperBase
{
    public HelloBootstrapper()
    {
        Initialize();
    }

    ...

    static Func<Type, DependencyObject, object, UIElement> _func;
    protected override void Configure()
    {
        var assembly = System.Reflection.Assembly.Load("WpfCustomControlLibrary1"); //<-- this is your assembly
        AssemblySource.Instance.Add(assembly);

        _func = ViewLocator.LocateForModelType;
        ViewLocator.LocateForModelType = LocateForModelType;
        ...
    }

    private static Func<Type, DependencyObject, object, UIElement> LocateForModelType = (modelType, displayLocation, context) => {

        //use the default method first:
        UIElement view = _func(modelType, displayLocation, context);
        if (!(view is TextBlock))
            return view;

        var viewTypeName = modelType.Name.Replace("Model", string.Empty);
        var viewType = (from assmebly in AssemblySource.Instance
                        from type in assmebly.GetExportedTypes()
                        where type.Name == viewTypeName
                        select type).FirstOrDefault();

        return viewType == null ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
            : Activator.CreateInstance(viewType) as UIElement;
    };
}