Using Caliburn.Micro binding feature on an "inner" user control
Asked Answered
A

3

5

I'm quite new to Caliburn.Micro, so I guess this has a simple answer (or at least I hope it has :))

I have a ViewModel with a property called ConnectedSystem that has a sub-property called Name.

In my View, I have the following XAML (excerpt):

<StackPanel Orientation="Horizontal">
  <Label Content="Name:" />
  <TextBlock x:Name="ConnectedSystem_Name" />
</StackPanel>

This works just fine, and the name is shown in the TextBlock as expected. However, ConnectedSystem has around 10 properties that should be displayed, and I don't want to copy-paste the XAML above 10 times inside my view. Instead I want to extract that XAML as a UserControl where I can set the LabelText and Text as properties.

I tried this, but I'm not sure how I can get Caliburn.Micro to pass the ConnectedSystem_Name into my UserControl automatically.

It might be a better way than using a UserControl here also, so the question is basically: What is the best way of putting this shared XAML as it's own control, and still be able to use Caliburn.Micros binding.

Adali answered 16/5, 2011 at 7:18 Comment(0)
F
3

Caliburn.Micro doesn't deal well with automatically linking multiple items to a single control. However you don't have to rely on the automatic binder, you can use a plain old wpf binding instead.

A UserControl is the way to go here. In the code you can add two DependencyProperties, LabelText and Text.

Then in the XAML for your UserControl, bind to the new properties.

Where you use this control you can now set the LabelText and Text values in XAML. So on your main view add the control, and bind LabelText and Text to the properties in your ViewModel.

Fronde answered 16/5, 2011 at 7:29 Comment(7)
Cameron: I tried this, and in my main view I set Text to {Binding ConnectedSystem_Name}, but I guess this is something that the WPF Bindign wont understand. How should I write this binding in a proper way?Florescence
{Binding ConnectedSystem.Name}Fronde
Cameron: Quite obvious actually ashamed. Thanks a lot. It worked out great now.Florescence
Cameron: Actually, it didn't work :(. I have the following code that works. <uc:SystemSetting LabelText="IP:" MainText="Test" />, but the following gives a blank text: <uc:SystemSetting LabelText="Name:" MainText="{Binding ConnectedSystem.Name}" />. However <TextBlock Text="{Binding ConnectedSystem.Name}"> shows the text as expected. Any idea?Florescence
Ah the joys of debugging bindings. Make sure the binding in the user control is two way.Fronde
No luck :(. Inside my user control, the XAML looks like this: <Label Content="{Binding LabelText, Mode=TwoWay}" />, and in the user control code behind, the DP is configured like this: public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register("LabelText", typeof(string), typeof(SystemSetting)); and the LabelText property use GetValue/SetValue, and when calling it, I call it like in my previous comment. And as mentioned, it works If I use a static text where I use the user control, but not if I use a binding when calling it. And yeah, a bitch to debug...Florescence
I'm trying to make deep binding work with action, hereAparejo
M
5

What you need to do is use a ContentControl in your main view to display the ConnectedSystem property of the main view model. By using a ContentControl you will get included in the view model binding process and the view model binder rules will be applied. So you want your property (using the default implementation of Caliburn) to be of type ConnectedSystemViewModel and have a view named ConnectedSystemView. Then in the view used to display the parent you want a ContentControl with an x:Name of ConnectedSystem (the name of the ConnectedSystemViewModel property. This will cause the view model binder to connect the two and do its usual work. Here is some code for clarity:

ConnectedSystemView.xaml (the user control that conventions will use when specifying ContentControl as the control to display the connected system property of main view model)

<UserControl x:Class="Sample.Views.ConnectedSystemView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <Label Grid.Column="0"
                   Grid.Row="0">PropertyOne Label:</Label>
        <TextBox x:Name="PropertyOne"
                 Grid.Column="1"
                 Grid.Row="0"></TextBox>

        <TextBlock Grid.Column="0"
                   Grid.Row="1">PropertyTwo Label:</TextBlock>
        <TextBox x:Name="PropertyTwo"
                 Grid.Column="1"
                 Grid.Row="1"></TextBox>

        <!-- repeat the TextBlock, TextBox pair for the remaining
             properties three through ten -->
    </Grid>
</UserControl>

ConnectedSystemViewModel.cs (the type of the ConnectedSystem property on your main view model)

namespace Sample.ViewModels
{
    public class ConnectedSystemViewModel : PropertyChangedBase
    {
        private string _propertyOne;
        public string PropertyOne
        {
            get { return _propertyOne; }
            set
            {
                _propertyOne = value;
                NotifyOfPropertyChange(() => PropertyOne);
            }
        }

        // these all need to be as above with NotifyPropertyChange,
        // omitted for brevity.
        public string PropertyTwo { get; set;}
        public string PropertyThree { get; set;}
        public string PropertyFour { get; set;}
        public string PropertyFive { get; set;}
        public string PropertySix { get; set;}
        public string PropertySeven { get; set;}
        public string PropertyEight { get; set;}
        public string PropertyNine { get; set;}
        public string PropertyTen { get; set;}
    }
}

And in your main view define a ContentControl named relative to the main view model property of type ConnectedSystemViewModel

<ContentControl x:Name="ConnectedSystem"></ContentControl>

If I understand you question correctly this should be all you need to hook into the default Caliburn.Micro conventions. Obviously you will add the 10 ConnectedSystem properties to ConnectedSystemViewModel and appropriate controls with appropriate names to ConnectedSystemView to complete the implementation.

This way inside your main view you only need to define the one ContentControl to display the ConnectedSytem property (instead of 10 identical custom user controls) and conventions will determine the type of user control to use to fill the ContentControl.

Inside the ConnectedSystemView which will be inserted to the content property of your main views ContentControl via conventions, you have the controls you want to display your 10 connected system properties.

Massif answered 16/5, 2011 at 23:5 Comment(5)
This is not actually what I want. I want to add the suer control 10 times inside the parent view. Not add 10 properties to the user control. Because the user control should be added to the parent view with different parameters each time. For example <uc:MyUserControlName Name="Test" Description="Test text" />. I need this because it will be 10 identical blocks of markup inside my main form now (except for name and description), and I want to replace them by 10 lines instead (the 10 calls to the user control).Florescence
But I appreciate your attempt, and if you have another suggestion I would be pleased to try it out :)Florescence
@Øyvind But aren't you just looking for a way to display a property of type ConnectedSystem of some other VM? Why do you want to add 10 user control instances to display a single ConnectedSystem property of your VM?Massif
I have a object with 10 different properties that should be displayed with it's own label, and in the same way. So I want to use this user control once for each of these properties, and have the output of them be formatted the same way. The 10 properties will be different aspects of an item that will be displayed by the main view.Florescence
@Øyvind Hmmm I must be missing something. I have created a small test project which works for this situation (as I understand it). I have made some edits to try and clarify my approach. Or is this actually not that my approach won't work, but just that you WANT to use 10 separate usercontrol instances on your main view to display the 1 ConnectedSystem property on your main view model?Massif
F
3

Caliburn.Micro doesn't deal well with automatically linking multiple items to a single control. However you don't have to rely on the automatic binder, you can use a plain old wpf binding instead.

A UserControl is the way to go here. In the code you can add two DependencyProperties, LabelText and Text.

Then in the XAML for your UserControl, bind to the new properties.

Where you use this control you can now set the LabelText and Text values in XAML. So on your main view add the control, and bind LabelText and Text to the properties in your ViewModel.

Fronde answered 16/5, 2011 at 7:29 Comment(7)
Cameron: I tried this, and in my main view I set Text to {Binding ConnectedSystem_Name}, but I guess this is something that the WPF Bindign wont understand. How should I write this binding in a proper way?Florescence
{Binding ConnectedSystem.Name}Fronde
Cameron: Quite obvious actually ashamed. Thanks a lot. It worked out great now.Florescence
Cameron: Actually, it didn't work :(. I have the following code that works. <uc:SystemSetting LabelText="IP:" MainText="Test" />, but the following gives a blank text: <uc:SystemSetting LabelText="Name:" MainText="{Binding ConnectedSystem.Name}" />. However <TextBlock Text="{Binding ConnectedSystem.Name}"> shows the text as expected. Any idea?Florescence
Ah the joys of debugging bindings. Make sure the binding in the user control is two way.Fronde
No luck :(. Inside my user control, the XAML looks like this: <Label Content="{Binding LabelText, Mode=TwoWay}" />, and in the user control code behind, the DP is configured like this: public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register("LabelText", typeof(string), typeof(SystemSetting)); and the LabelText property use GetValue/SetValue, and when calling it, I call it like in my previous comment. And as mentioned, it works If I use a static text where I use the user control, but not if I use a binding when calling it. And yeah, a bitch to debug...Florescence
I'm trying to make deep binding work with action, hereAparejo
M
0

If you just want to display or set the property, my approach is like this:

public String ConnectedSystemName
{
    get { return _connectedSystem.Name; }
    set
    {
        _connectedSystem.Name = value;
        NotifyOfPropertyChange(() => _connectedSystem.Name);
    }
}

If you want to display or set the property from parent of user control, you can create attached property to bind to user control which is it get/set the property from user control

Melt answered 6/7, 2012 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.